diff options
Diffstat (limited to 'examples/redis-unstable/deps/jemalloc/src')
65 files changed, 0 insertions, 32813 deletions
diff --git a/examples/redis-unstable/deps/jemalloc/src/arena.c b/examples/redis-unstable/deps/jemalloc/src/arena.c deleted file mode 100644 index 857b27c..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/arena.c +++ /dev/null | |||
| @@ -1,1891 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/decay.h" | ||
| 6 | #include "jemalloc/internal/ehooks.h" | ||
| 7 | #include "jemalloc/internal/extent_dss.h" | ||
| 8 | #include "jemalloc/internal/extent_mmap.h" | ||
| 9 | #include "jemalloc/internal/san.h" | ||
| 10 | #include "jemalloc/internal/mutex.h" | ||
| 11 | #include "jemalloc/internal/rtree.h" | ||
| 12 | #include "jemalloc/internal/safety_check.h" | ||
| 13 | #include "jemalloc/internal/util.h" | ||
| 14 | |||
| 15 | JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS | ||
| 16 | |||
| 17 | /******************************************************************************/ | ||
| 18 | /* Data. */ | ||
| 19 | |||
| 20 | /* | ||
| 21 | * Define names for both unininitialized and initialized phases, so that | ||
| 22 | * options and mallctl processing are straightforward. | ||
| 23 | */ | ||
| 24 | const char *percpu_arena_mode_names[] = { | ||
| 25 | "percpu", | ||
| 26 | "phycpu", | ||
| 27 | "disabled", | ||
| 28 | "percpu", | ||
| 29 | "phycpu" | ||
| 30 | }; | ||
| 31 | percpu_arena_mode_t opt_percpu_arena = PERCPU_ARENA_DEFAULT; | ||
| 32 | |||
| 33 | ssize_t opt_dirty_decay_ms = DIRTY_DECAY_MS_DEFAULT; | ||
| 34 | ssize_t opt_muzzy_decay_ms = MUZZY_DECAY_MS_DEFAULT; | ||
| 35 | |||
| 36 | static atomic_zd_t dirty_decay_ms_default; | ||
| 37 | static atomic_zd_t muzzy_decay_ms_default; | ||
| 38 | |||
| 39 | emap_t arena_emap_global; | ||
| 40 | pa_central_t arena_pa_central_global; | ||
| 41 | |||
| 42 | div_info_t arena_binind_div_info[SC_NBINS]; | ||
| 43 | |||
| 44 | size_t opt_oversize_threshold = OVERSIZE_THRESHOLD_DEFAULT; | ||
| 45 | size_t oversize_threshold = OVERSIZE_THRESHOLD_DEFAULT; | ||
| 46 | |||
| 47 | uint32_t arena_bin_offsets[SC_NBINS]; | ||
| 48 | static unsigned nbins_total; | ||
| 49 | |||
| 50 | static unsigned huge_arena_ind; | ||
| 51 | |||
| 52 | const arena_config_t arena_config_default = { | ||
| 53 | /* .extent_hooks = */ (extent_hooks_t *)&ehooks_default_extent_hooks, | ||
| 54 | /* .metadata_use_hooks = */ true, | ||
| 55 | }; | ||
| 56 | |||
| 57 | /******************************************************************************/ | ||
| 58 | /* | ||
| 59 | * Function prototypes for static functions that are referenced prior to | ||
| 60 | * definition. | ||
| 61 | */ | ||
| 62 | |||
| 63 | static bool arena_decay_dirty(tsdn_t *tsdn, arena_t *arena, | ||
| 64 | bool is_background_thread, bool all); | ||
| 65 | static void arena_bin_lower_slab(tsdn_t *tsdn, arena_t *arena, edata_t *slab, | ||
| 66 | bin_t *bin); | ||
| 67 | static void | ||
| 68 | arena_maybe_do_deferred_work(tsdn_t *tsdn, arena_t *arena, decay_t *decay, | ||
| 69 | size_t npages_new); | ||
| 70 | |||
| 71 | /******************************************************************************/ | ||
| 72 | |||
| 73 | void | ||
| 74 | arena_basic_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads, | ||
| 75 | const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms, | ||
| 76 | size_t *nactive, size_t *ndirty, size_t *nmuzzy) { | ||
| 77 | *nthreads += arena_nthreads_get(arena, false); | ||
| 78 | *dss = dss_prec_names[arena_dss_prec_get(arena)]; | ||
| 79 | *dirty_decay_ms = arena_decay_ms_get(arena, extent_state_dirty); | ||
| 80 | *muzzy_decay_ms = arena_decay_ms_get(arena, extent_state_muzzy); | ||
| 81 | pa_shard_basic_stats_merge(&arena->pa_shard, nactive, ndirty, nmuzzy); | ||
| 82 | } | ||
| 83 | |||
| 84 | void | ||
| 85 | arena_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads, | ||
| 86 | const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms, | ||
| 87 | size_t *nactive, size_t *ndirty, size_t *nmuzzy, arena_stats_t *astats, | ||
| 88 | bin_stats_data_t *bstats, arena_stats_large_t *lstats, | ||
| 89 | pac_estats_t *estats, hpa_shard_stats_t *hpastats, sec_stats_t *secstats) { | ||
| 90 | cassert(config_stats); | ||
| 91 | |||
| 92 | arena_basic_stats_merge(tsdn, arena, nthreads, dss, dirty_decay_ms, | ||
| 93 | muzzy_decay_ms, nactive, ndirty, nmuzzy); | ||
| 94 | |||
| 95 | size_t base_allocated, base_resident, base_mapped, metadata_thp; | ||
| 96 | base_stats_get(tsdn, arena->base, &base_allocated, &base_resident, | ||
| 97 | &base_mapped, &metadata_thp); | ||
| 98 | size_t pac_mapped_sz = pac_mapped(&arena->pa_shard.pac); | ||
| 99 | astats->mapped += base_mapped + pac_mapped_sz; | ||
| 100 | astats->resident += base_resident; | ||
| 101 | |||
| 102 | LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); | ||
| 103 | |||
| 104 | astats->base += base_allocated; | ||
| 105 | atomic_load_add_store_zu(&astats->internal, arena_internal_get(arena)); | ||
| 106 | astats->metadata_thp += metadata_thp; | ||
| 107 | |||
| 108 | for (szind_t i = 0; i < SC_NSIZES - SC_NBINS; i++) { | ||
| 109 | uint64_t nmalloc = locked_read_u64(tsdn, | ||
| 110 | LOCKEDINT_MTX(arena->stats.mtx), | ||
| 111 | &arena->stats.lstats[i].nmalloc); | ||
| 112 | locked_inc_u64_unsynchronized(&lstats[i].nmalloc, nmalloc); | ||
| 113 | astats->nmalloc_large += nmalloc; | ||
| 114 | |||
| 115 | uint64_t ndalloc = locked_read_u64(tsdn, | ||
| 116 | LOCKEDINT_MTX(arena->stats.mtx), | ||
| 117 | &arena->stats.lstats[i].ndalloc); | ||
| 118 | locked_inc_u64_unsynchronized(&lstats[i].ndalloc, ndalloc); | ||
| 119 | astats->ndalloc_large += ndalloc; | ||
| 120 | |||
| 121 | uint64_t nrequests = locked_read_u64(tsdn, | ||
| 122 | LOCKEDINT_MTX(arena->stats.mtx), | ||
| 123 | &arena->stats.lstats[i].nrequests); | ||
| 124 | locked_inc_u64_unsynchronized(&lstats[i].nrequests, | ||
| 125 | nmalloc + nrequests); | ||
| 126 | astats->nrequests_large += nmalloc + nrequests; | ||
| 127 | |||
| 128 | /* nfill == nmalloc for large currently. */ | ||
| 129 | locked_inc_u64_unsynchronized(&lstats[i].nfills, nmalloc); | ||
| 130 | astats->nfills_large += nmalloc; | ||
| 131 | |||
| 132 | uint64_t nflush = locked_read_u64(tsdn, | ||
| 133 | LOCKEDINT_MTX(arena->stats.mtx), | ||
| 134 | &arena->stats.lstats[i].nflushes); | ||
| 135 | locked_inc_u64_unsynchronized(&lstats[i].nflushes, nflush); | ||
| 136 | astats->nflushes_large += nflush; | ||
| 137 | |||
| 138 | assert(nmalloc >= ndalloc); | ||
| 139 | assert(nmalloc - ndalloc <= SIZE_T_MAX); | ||
| 140 | size_t curlextents = (size_t)(nmalloc - ndalloc); | ||
| 141 | lstats[i].curlextents += curlextents; | ||
| 142 | astats->allocated_large += | ||
| 143 | curlextents * sz_index2size(SC_NBINS + i); | ||
| 144 | } | ||
| 145 | |||
| 146 | pa_shard_stats_merge(tsdn, &arena->pa_shard, &astats->pa_shard_stats, | ||
| 147 | estats, hpastats, secstats, &astats->resident); | ||
| 148 | |||
| 149 | LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); | ||
| 150 | |||
| 151 | /* Currently cached bytes and sanitizer-stashed bytes in tcache. */ | ||
| 152 | astats->tcache_bytes = 0; | ||
| 153 | astats->tcache_stashed_bytes = 0; | ||
| 154 | malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); | ||
| 155 | cache_bin_array_descriptor_t *descriptor; | ||
| 156 | ql_foreach(descriptor, &arena->cache_bin_array_descriptor_ql, link) { | ||
| 157 | for (szind_t i = 0; i < nhbins; i++) { | ||
| 158 | cache_bin_t *cache_bin = &descriptor->bins[i]; | ||
| 159 | cache_bin_sz_t ncached, nstashed; | ||
| 160 | cache_bin_nitems_get_remote(cache_bin, | ||
| 161 | &tcache_bin_info[i], &ncached, &nstashed); | ||
| 162 | |||
| 163 | astats->tcache_bytes += ncached * sz_index2size(i); | ||
| 164 | astats->tcache_stashed_bytes += nstashed * | ||
| 165 | sz_index2size(i); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | malloc_mutex_prof_read(tsdn, | ||
| 169 | &astats->mutex_prof_data[arena_prof_mutex_tcache_list], | ||
| 170 | &arena->tcache_ql_mtx); | ||
| 171 | malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); | ||
| 172 | |||
| 173 | #define READ_ARENA_MUTEX_PROF_DATA(mtx, ind) \ | ||
| 174 | malloc_mutex_lock(tsdn, &arena->mtx); \ | ||
| 175 | malloc_mutex_prof_read(tsdn, &astats->mutex_prof_data[ind], \ | ||
| 176 | &arena->mtx); \ | ||
| 177 | malloc_mutex_unlock(tsdn, &arena->mtx); | ||
| 178 | |||
| 179 | /* Gather per arena mutex profiling data. */ | ||
| 180 | READ_ARENA_MUTEX_PROF_DATA(large_mtx, arena_prof_mutex_large); | ||
| 181 | READ_ARENA_MUTEX_PROF_DATA(base->mtx, | ||
| 182 | arena_prof_mutex_base); | ||
| 183 | #undef READ_ARENA_MUTEX_PROF_DATA | ||
| 184 | pa_shard_mtx_stats_read(tsdn, &arena->pa_shard, | ||
| 185 | astats->mutex_prof_data); | ||
| 186 | |||
| 187 | nstime_copy(&astats->uptime, &arena->create_time); | ||
| 188 | nstime_update(&astats->uptime); | ||
| 189 | nstime_subtract(&astats->uptime, &arena->create_time); | ||
| 190 | |||
| 191 | for (szind_t i = 0; i < SC_NBINS; i++) { | ||
| 192 | for (unsigned j = 0; j < bin_infos[i].n_shards; j++) { | ||
| 193 | bin_stats_merge(tsdn, &bstats[i], | ||
| 194 | arena_get_bin(arena, i, j)); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | static void | ||
| 200 | arena_background_thread_inactivity_check(tsdn_t *tsdn, arena_t *arena, | ||
| 201 | bool is_background_thread) { | ||
| 202 | if (!background_thread_enabled() || is_background_thread) { | ||
| 203 | return; | ||
| 204 | } | ||
| 205 | background_thread_info_t *info = | ||
| 206 | arena_background_thread_info_get(arena); | ||
| 207 | if (background_thread_indefinite_sleep(info)) { | ||
| 208 | arena_maybe_do_deferred_work(tsdn, arena, | ||
| 209 | &arena->pa_shard.pac.decay_dirty, 0); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | /* | ||
| 214 | * React to deferred work generated by a PAI function. | ||
| 215 | */ | ||
| 216 | void arena_handle_deferred_work(tsdn_t *tsdn, arena_t *arena) { | ||
| 217 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 218 | WITNESS_RANK_CORE, 0); | ||
| 219 | |||
| 220 | if (decay_immediately(&arena->pa_shard.pac.decay_dirty)) { | ||
| 221 | arena_decay_dirty(tsdn, arena, false, true); | ||
| 222 | } | ||
| 223 | arena_background_thread_inactivity_check(tsdn, arena, false); | ||
| 224 | } | ||
| 225 | |||
| 226 | static void * | ||
| 227 | arena_slab_reg_alloc(edata_t *slab, const bin_info_t *bin_info) { | ||
| 228 | void *ret; | ||
| 229 | slab_data_t *slab_data = edata_slab_data_get(slab); | ||
| 230 | size_t regind; | ||
| 231 | |||
| 232 | assert(edata_nfree_get(slab) > 0); | ||
| 233 | assert(!bitmap_full(slab_data->bitmap, &bin_info->bitmap_info)); | ||
| 234 | |||
| 235 | regind = bitmap_sfu(slab_data->bitmap, &bin_info->bitmap_info); | ||
| 236 | ret = (void *)((uintptr_t)edata_addr_get(slab) + | ||
| 237 | (uintptr_t)(bin_info->reg_size * regind)); | ||
| 238 | edata_nfree_dec(slab); | ||
| 239 | return ret; | ||
| 240 | } | ||
| 241 | |||
| 242 | static void | ||
| 243 | arena_slab_reg_alloc_batch(edata_t *slab, const bin_info_t *bin_info, | ||
| 244 | unsigned cnt, void** ptrs) { | ||
| 245 | slab_data_t *slab_data = edata_slab_data_get(slab); | ||
| 246 | |||
| 247 | assert(edata_nfree_get(slab) >= cnt); | ||
| 248 | assert(!bitmap_full(slab_data->bitmap, &bin_info->bitmap_info)); | ||
| 249 | |||
| 250 | #if (! defined JEMALLOC_INTERNAL_POPCOUNTL) || (defined BITMAP_USE_TREE) | ||
| 251 | for (unsigned i = 0; i < cnt; i++) { | ||
| 252 | size_t regind = bitmap_sfu(slab_data->bitmap, | ||
| 253 | &bin_info->bitmap_info); | ||
| 254 | *(ptrs + i) = (void *)((uintptr_t)edata_addr_get(slab) + | ||
| 255 | (uintptr_t)(bin_info->reg_size * regind)); | ||
| 256 | } | ||
| 257 | #else | ||
| 258 | unsigned group = 0; | ||
| 259 | bitmap_t g = slab_data->bitmap[group]; | ||
| 260 | unsigned i = 0; | ||
| 261 | while (i < cnt) { | ||
| 262 | while (g == 0) { | ||
| 263 | g = slab_data->bitmap[++group]; | ||
| 264 | } | ||
| 265 | size_t shift = group << LG_BITMAP_GROUP_NBITS; | ||
| 266 | size_t pop = popcount_lu(g); | ||
| 267 | if (pop > (cnt - i)) { | ||
| 268 | pop = cnt - i; | ||
| 269 | } | ||
| 270 | |||
| 271 | /* | ||
| 272 | * Load from memory locations only once, outside the | ||
| 273 | * hot loop below. | ||
| 274 | */ | ||
| 275 | uintptr_t base = (uintptr_t)edata_addr_get(slab); | ||
| 276 | uintptr_t regsize = (uintptr_t)bin_info->reg_size; | ||
| 277 | while (pop--) { | ||
| 278 | size_t bit = cfs_lu(&g); | ||
| 279 | size_t regind = shift + bit; | ||
| 280 | *(ptrs + i) = (void *)(base + regsize * regind); | ||
| 281 | |||
| 282 | i++; | ||
| 283 | } | ||
| 284 | slab_data->bitmap[group] = g; | ||
| 285 | } | ||
| 286 | #endif | ||
| 287 | edata_nfree_sub(slab, cnt); | ||
| 288 | } | ||
| 289 | |||
| 290 | static void | ||
| 291 | arena_large_malloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t usize) { | ||
| 292 | szind_t index, hindex; | ||
| 293 | |||
| 294 | cassert(config_stats); | ||
| 295 | |||
| 296 | if (usize < SC_LARGE_MINCLASS) { | ||
| 297 | usize = SC_LARGE_MINCLASS; | ||
| 298 | } | ||
| 299 | index = sz_size2index(usize); | ||
| 300 | hindex = (index >= SC_NBINS) ? index - SC_NBINS : 0; | ||
| 301 | |||
| 302 | locked_inc_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), | ||
| 303 | &arena->stats.lstats[hindex].nmalloc, 1); | ||
| 304 | } | ||
| 305 | |||
| 306 | static void | ||
| 307 | arena_large_dalloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t usize) { | ||
| 308 | szind_t index, hindex; | ||
| 309 | |||
| 310 | cassert(config_stats); | ||
| 311 | |||
| 312 | if (usize < SC_LARGE_MINCLASS) { | ||
| 313 | usize = SC_LARGE_MINCLASS; | ||
| 314 | } | ||
| 315 | index = sz_size2index(usize); | ||
| 316 | hindex = (index >= SC_NBINS) ? index - SC_NBINS : 0; | ||
| 317 | |||
| 318 | locked_inc_u64(tsdn, LOCKEDINT_MTX(arena->stats.mtx), | ||
| 319 | &arena->stats.lstats[hindex].ndalloc, 1); | ||
| 320 | } | ||
| 321 | |||
| 322 | static void | ||
| 323 | arena_large_ralloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t oldusize, | ||
| 324 | size_t usize) { | ||
| 325 | arena_large_malloc_stats_update(tsdn, arena, usize); | ||
| 326 | arena_large_dalloc_stats_update(tsdn, arena, oldusize); | ||
| 327 | } | ||
| 328 | |||
| 329 | edata_t * | ||
| 330 | arena_extent_alloc_large(tsdn_t *tsdn, arena_t *arena, size_t usize, | ||
| 331 | size_t alignment, bool zero) { | ||
| 332 | bool deferred_work_generated = false; | ||
| 333 | szind_t szind = sz_size2index(usize); | ||
| 334 | size_t esize = usize + sz_large_pad; | ||
| 335 | |||
| 336 | bool guarded = san_large_extent_decide_guard(tsdn, | ||
| 337 | arena_get_ehooks(arena), esize, alignment); | ||
| 338 | edata_t *edata = pa_alloc(tsdn, &arena->pa_shard, esize, alignment, | ||
| 339 | /* slab */ false, szind, zero, guarded, &deferred_work_generated); | ||
| 340 | assert(deferred_work_generated == false); | ||
| 341 | |||
| 342 | if (edata != NULL) { | ||
| 343 | if (config_stats) { | ||
| 344 | LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); | ||
| 345 | arena_large_malloc_stats_update(tsdn, arena, usize); | ||
| 346 | LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | if (edata != NULL && sz_large_pad != 0) { | ||
| 351 | arena_cache_oblivious_randomize(tsdn, arena, edata, alignment); | ||
| 352 | } | ||
| 353 | |||
| 354 | return edata; | ||
| 355 | } | ||
| 356 | |||
| 357 | void | ||
| 358 | arena_extent_dalloc_large_prep(tsdn_t *tsdn, arena_t *arena, edata_t *edata) { | ||
| 359 | if (config_stats) { | ||
| 360 | LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); | ||
| 361 | arena_large_dalloc_stats_update(tsdn, arena, | ||
| 362 | edata_usize_get(edata)); | ||
| 363 | LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | |||
| 367 | void | ||
| 368 | arena_extent_ralloc_large_shrink(tsdn_t *tsdn, arena_t *arena, edata_t *edata, | ||
| 369 | size_t oldusize) { | ||
| 370 | size_t usize = edata_usize_get(edata); | ||
| 371 | |||
| 372 | if (config_stats) { | ||
| 373 | LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); | ||
| 374 | arena_large_ralloc_stats_update(tsdn, arena, oldusize, usize); | ||
| 375 | LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | |||
| 379 | void | ||
| 380 | arena_extent_ralloc_large_expand(tsdn_t *tsdn, arena_t *arena, edata_t *edata, | ||
| 381 | size_t oldusize) { | ||
| 382 | size_t usize = edata_usize_get(edata); | ||
| 383 | |||
| 384 | if (config_stats) { | ||
| 385 | LOCKEDINT_MTX_LOCK(tsdn, arena->stats.mtx); | ||
| 386 | arena_large_ralloc_stats_update(tsdn, arena, oldusize, usize); | ||
| 387 | LOCKEDINT_MTX_UNLOCK(tsdn, arena->stats.mtx); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | /* | ||
| 392 | * In situations where we're not forcing a decay (i.e. because the user | ||
| 393 | * specifically requested it), should we purge ourselves, or wait for the | ||
| 394 | * background thread to get to it. | ||
| 395 | */ | ||
| 396 | static pac_purge_eagerness_t | ||
| 397 | arena_decide_unforced_purge_eagerness(bool is_background_thread) { | ||
| 398 | if (is_background_thread) { | ||
| 399 | return PAC_PURGE_ALWAYS; | ||
| 400 | } else if (!is_background_thread && background_thread_enabled()) { | ||
| 401 | return PAC_PURGE_NEVER; | ||
| 402 | } else { | ||
| 403 | return PAC_PURGE_ON_EPOCH_ADVANCE; | ||
| 404 | } | ||
| 405 | } | ||
| 406 | |||
| 407 | bool | ||
| 408 | arena_decay_ms_set(tsdn_t *tsdn, arena_t *arena, extent_state_t state, | ||
| 409 | ssize_t decay_ms) { | ||
| 410 | pac_purge_eagerness_t eagerness = arena_decide_unforced_purge_eagerness( | ||
| 411 | /* is_background_thread */ false); | ||
| 412 | return pa_decay_ms_set(tsdn, &arena->pa_shard, state, decay_ms, | ||
| 413 | eagerness); | ||
| 414 | } | ||
| 415 | |||
| 416 | ssize_t | ||
| 417 | arena_decay_ms_get(arena_t *arena, extent_state_t state) { | ||
| 418 | return pa_decay_ms_get(&arena->pa_shard, state); | ||
| 419 | } | ||
| 420 | |||
| 421 | static bool | ||
| 422 | arena_decay_impl(tsdn_t *tsdn, arena_t *arena, decay_t *decay, | ||
| 423 | pac_decay_stats_t *decay_stats, ecache_t *ecache, | ||
| 424 | bool is_background_thread, bool all) { | ||
| 425 | if (all) { | ||
| 426 | malloc_mutex_lock(tsdn, &decay->mtx); | ||
| 427 | pac_decay_all(tsdn, &arena->pa_shard.pac, decay, decay_stats, | ||
| 428 | ecache, /* fully_decay */ all); | ||
| 429 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 430 | return false; | ||
| 431 | } | ||
| 432 | |||
| 433 | if (malloc_mutex_trylock(tsdn, &decay->mtx)) { | ||
| 434 | /* No need to wait if another thread is in progress. */ | ||
| 435 | return true; | ||
| 436 | } | ||
| 437 | pac_purge_eagerness_t eagerness = | ||
| 438 | arena_decide_unforced_purge_eagerness(is_background_thread); | ||
| 439 | bool epoch_advanced = pac_maybe_decay_purge(tsdn, &arena->pa_shard.pac, | ||
| 440 | decay, decay_stats, ecache, eagerness); | ||
| 441 | size_t npages_new; | ||
| 442 | if (epoch_advanced) { | ||
| 443 | /* Backlog is updated on epoch advance. */ | ||
| 444 | npages_new = decay_epoch_npages_delta(decay); | ||
| 445 | } | ||
| 446 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 447 | |||
| 448 | if (have_background_thread && background_thread_enabled() && | ||
| 449 | epoch_advanced && !is_background_thread) { | ||
| 450 | arena_maybe_do_deferred_work(tsdn, arena, decay, npages_new); | ||
| 451 | } | ||
| 452 | |||
| 453 | return false; | ||
| 454 | } | ||
| 455 | |||
| 456 | static bool | ||
| 457 | arena_decay_dirty(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, | ||
| 458 | bool all) { | ||
| 459 | return arena_decay_impl(tsdn, arena, &arena->pa_shard.pac.decay_dirty, | ||
| 460 | &arena->pa_shard.pac.stats->decay_dirty, | ||
| 461 | &arena->pa_shard.pac.ecache_dirty, is_background_thread, all); | ||
| 462 | } | ||
| 463 | |||
| 464 | static bool | ||
| 465 | arena_decay_muzzy(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, | ||
| 466 | bool all) { | ||
| 467 | if (pa_shard_dont_decay_muzzy(&arena->pa_shard)) { | ||
| 468 | return false; | ||
| 469 | } | ||
| 470 | return arena_decay_impl(tsdn, arena, &arena->pa_shard.pac.decay_muzzy, | ||
| 471 | &arena->pa_shard.pac.stats->decay_muzzy, | ||
| 472 | &arena->pa_shard.pac.ecache_muzzy, is_background_thread, all); | ||
| 473 | } | ||
| 474 | |||
| 475 | void | ||
| 476 | arena_decay(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all) { | ||
| 477 | if (all) { | ||
| 478 | /* | ||
| 479 | * We should take a purge of "all" to mean "save as much memory | ||
| 480 | * as possible", including flushing any caches (for situations | ||
| 481 | * like thread death, or manual purge calls). | ||
| 482 | */ | ||
| 483 | sec_flush(tsdn, &arena->pa_shard.hpa_sec); | ||
| 484 | } | ||
| 485 | if (arena_decay_dirty(tsdn, arena, is_background_thread, all)) { | ||
| 486 | return; | ||
| 487 | } | ||
| 488 | arena_decay_muzzy(tsdn, arena, is_background_thread, all); | ||
| 489 | } | ||
| 490 | |||
| 491 | static bool | ||
| 492 | arena_should_decay_early(tsdn_t *tsdn, arena_t *arena, decay_t *decay, | ||
| 493 | background_thread_info_t *info, nstime_t *remaining_sleep, | ||
| 494 | size_t npages_new) { | ||
| 495 | malloc_mutex_assert_owner(tsdn, &info->mtx); | ||
| 496 | |||
| 497 | if (malloc_mutex_trylock(tsdn, &decay->mtx)) { | ||
| 498 | return false; | ||
| 499 | } | ||
| 500 | |||
| 501 | if (!decay_gradually(decay)) { | ||
| 502 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 503 | return false; | ||
| 504 | } | ||
| 505 | |||
| 506 | nstime_init(remaining_sleep, background_thread_wakeup_time_get(info)); | ||
| 507 | if (nstime_compare(remaining_sleep, &decay->epoch) <= 0) { | ||
| 508 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 509 | return false; | ||
| 510 | } | ||
| 511 | nstime_subtract(remaining_sleep, &decay->epoch); | ||
| 512 | if (npages_new > 0) { | ||
| 513 | uint64_t npurge_new = decay_npages_purge_in(decay, | ||
| 514 | remaining_sleep, npages_new); | ||
| 515 | info->npages_to_purge_new += npurge_new; | ||
| 516 | } | ||
| 517 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 518 | return info->npages_to_purge_new > | ||
| 519 | ARENA_DEFERRED_PURGE_NPAGES_THRESHOLD; | ||
| 520 | } | ||
| 521 | |||
| 522 | /* | ||
| 523 | * Check if deferred work needs to be done sooner than planned. | ||
| 524 | * For decay we might want to wake up earlier because of an influx of dirty | ||
| 525 | * pages. Rather than waiting for previously estimated time, we proactively | ||
| 526 | * purge those pages. | ||
| 527 | * If background thread sleeps indefinitely, always wake up because some | ||
| 528 | * deferred work has been generated. | ||
| 529 | */ | ||
| 530 | static void | ||
| 531 | arena_maybe_do_deferred_work(tsdn_t *tsdn, arena_t *arena, decay_t *decay, | ||
| 532 | size_t npages_new) { | ||
| 533 | background_thread_info_t *info = arena_background_thread_info_get( | ||
| 534 | arena); | ||
| 535 | if (malloc_mutex_trylock(tsdn, &info->mtx)) { | ||
| 536 | /* | ||
| 537 | * Background thread may hold the mutex for a long period of | ||
| 538 | * time. We'd like to avoid the variance on application | ||
| 539 | * threads. So keep this non-blocking, and leave the work to a | ||
| 540 | * future epoch. | ||
| 541 | */ | ||
| 542 | return; | ||
| 543 | } | ||
| 544 | if (!background_thread_is_started(info)) { | ||
| 545 | goto label_done; | ||
| 546 | } | ||
| 547 | |||
| 548 | nstime_t remaining_sleep; | ||
| 549 | if (background_thread_indefinite_sleep(info)) { | ||
| 550 | background_thread_wakeup_early(info, NULL); | ||
| 551 | } else if (arena_should_decay_early(tsdn, arena, decay, info, | ||
| 552 | &remaining_sleep, npages_new)) { | ||
| 553 | info->npages_to_purge_new = 0; | ||
| 554 | background_thread_wakeup_early(info, &remaining_sleep); | ||
| 555 | } | ||
| 556 | label_done: | ||
| 557 | malloc_mutex_unlock(tsdn, &info->mtx); | ||
| 558 | } | ||
| 559 | |||
| 560 | /* Called from background threads. */ | ||
| 561 | void | ||
| 562 | arena_do_deferred_work(tsdn_t *tsdn, arena_t *arena) { | ||
| 563 | arena_decay(tsdn, arena, true, false); | ||
| 564 | pa_shard_do_deferred_work(tsdn, &arena->pa_shard); | ||
| 565 | } | ||
| 566 | |||
| 567 | void | ||
| 568 | arena_slab_dalloc(tsdn_t *tsdn, arena_t *arena, edata_t *slab) { | ||
| 569 | bool deferred_work_generated = false; | ||
| 570 | pa_dalloc(tsdn, &arena->pa_shard, slab, &deferred_work_generated); | ||
| 571 | if (deferred_work_generated) { | ||
| 572 | arena_handle_deferred_work(tsdn, arena); | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | static void | ||
| 577 | arena_bin_slabs_nonfull_insert(bin_t *bin, edata_t *slab) { | ||
| 578 | assert(edata_nfree_get(slab) > 0); | ||
| 579 | edata_heap_insert(&bin->slabs_nonfull, slab); | ||
| 580 | if (config_stats) { | ||
| 581 | bin->stats.nonfull_slabs++; | ||
| 582 | } | ||
| 583 | } | ||
| 584 | |||
| 585 | static void | ||
| 586 | arena_bin_slabs_nonfull_remove(bin_t *bin, edata_t *slab) { | ||
| 587 | edata_heap_remove(&bin->slabs_nonfull, slab); | ||
| 588 | if (config_stats) { | ||
| 589 | bin->stats.nonfull_slabs--; | ||
| 590 | } | ||
| 591 | } | ||
| 592 | |||
| 593 | static edata_t * | ||
| 594 | arena_bin_slabs_nonfull_tryget(bin_t *bin) { | ||
| 595 | edata_t *slab = edata_heap_remove_first(&bin->slabs_nonfull); | ||
| 596 | if (slab == NULL) { | ||
| 597 | return NULL; | ||
| 598 | } | ||
| 599 | if (config_stats) { | ||
| 600 | bin->stats.reslabs++; | ||
| 601 | bin->stats.nonfull_slabs--; | ||
| 602 | } | ||
| 603 | return slab; | ||
| 604 | } | ||
| 605 | |||
| 606 | static void | ||
| 607 | arena_bin_slabs_full_insert(arena_t *arena, bin_t *bin, edata_t *slab) { | ||
| 608 | assert(edata_nfree_get(slab) == 0); | ||
| 609 | /* | ||
| 610 | * Tracking extents is required by arena_reset, which is not allowed | ||
| 611 | * for auto arenas. Bypass this step to avoid touching the edata | ||
| 612 | * linkage (often results in cache misses) for auto arenas. | ||
| 613 | */ | ||
| 614 | if (arena_is_auto(arena)) { | ||
| 615 | return; | ||
| 616 | } | ||
| 617 | edata_list_active_append(&bin->slabs_full, slab); | ||
| 618 | } | ||
| 619 | |||
| 620 | static void | ||
| 621 | arena_bin_slabs_full_remove(arena_t *arena, bin_t *bin, edata_t *slab) { | ||
| 622 | if (arena_is_auto(arena)) { | ||
| 623 | return; | ||
| 624 | } | ||
| 625 | edata_list_active_remove(&bin->slabs_full, slab); | ||
| 626 | } | ||
| 627 | |||
| 628 | static void | ||
| 629 | arena_bin_reset(tsd_t *tsd, arena_t *arena, bin_t *bin) { | ||
| 630 | edata_t *slab; | ||
| 631 | |||
| 632 | malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); | ||
| 633 | if (bin->slabcur != NULL) { | ||
| 634 | slab = bin->slabcur; | ||
| 635 | bin->slabcur = NULL; | ||
| 636 | malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); | ||
| 637 | arena_slab_dalloc(tsd_tsdn(tsd), arena, slab); | ||
| 638 | malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); | ||
| 639 | } | ||
| 640 | while ((slab = edata_heap_remove_first(&bin->slabs_nonfull)) != NULL) { | ||
| 641 | malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); | ||
| 642 | arena_slab_dalloc(tsd_tsdn(tsd), arena, slab); | ||
| 643 | malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); | ||
| 644 | } | ||
| 645 | for (slab = edata_list_active_first(&bin->slabs_full); slab != NULL; | ||
| 646 | slab = edata_list_active_first(&bin->slabs_full)) { | ||
| 647 | arena_bin_slabs_full_remove(arena, bin, slab); | ||
| 648 | malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); | ||
| 649 | arena_slab_dalloc(tsd_tsdn(tsd), arena, slab); | ||
| 650 | malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock); | ||
| 651 | } | ||
| 652 | if (config_stats) { | ||
| 653 | bin->stats.curregs = 0; | ||
| 654 | bin->stats.curslabs = 0; | ||
| 655 | } | ||
| 656 | malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock); | ||
| 657 | } | ||
| 658 | |||
| 659 | void | ||
| 660 | arena_reset(tsd_t *tsd, arena_t *arena) { | ||
| 661 | /* | ||
| 662 | * Locking in this function is unintuitive. The caller guarantees that | ||
| 663 | * no concurrent operations are happening in this arena, but there are | ||
| 664 | * still reasons that some locking is necessary: | ||
| 665 | * | ||
| 666 | * - Some of the functions in the transitive closure of calls assume | ||
| 667 | * appropriate locks are held, and in some cases these locks are | ||
| 668 | * temporarily dropped to avoid lock order reversal or deadlock due to | ||
| 669 | * reentry. | ||
| 670 | * - mallctl("epoch", ...) may concurrently refresh stats. While | ||
| 671 | * strictly speaking this is a "concurrent operation", disallowing | ||
| 672 | * stats refreshes would impose an inconvenient burden. | ||
| 673 | */ | ||
| 674 | |||
| 675 | /* Large allocations. */ | ||
| 676 | malloc_mutex_lock(tsd_tsdn(tsd), &arena->large_mtx); | ||
| 677 | |||
| 678 | for (edata_t *edata = edata_list_active_first(&arena->large); | ||
| 679 | edata != NULL; edata = edata_list_active_first(&arena->large)) { | ||
| 680 | void *ptr = edata_base_get(edata); | ||
| 681 | size_t usize; | ||
| 682 | |||
| 683 | malloc_mutex_unlock(tsd_tsdn(tsd), &arena->large_mtx); | ||
| 684 | emap_alloc_ctx_t alloc_ctx; | ||
| 685 | emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, | ||
| 686 | &alloc_ctx); | ||
| 687 | assert(alloc_ctx.szind != SC_NSIZES); | ||
| 688 | |||
| 689 | if (config_stats || (config_prof && opt_prof)) { | ||
| 690 | usize = sz_index2size(alloc_ctx.szind); | ||
| 691 | assert(usize == isalloc(tsd_tsdn(tsd), ptr)); | ||
| 692 | } | ||
| 693 | /* Remove large allocation from prof sample set. */ | ||
| 694 | if (config_prof && opt_prof) { | ||
| 695 | prof_free(tsd, ptr, usize, &alloc_ctx); | ||
| 696 | } | ||
| 697 | large_dalloc(tsd_tsdn(tsd), edata); | ||
| 698 | malloc_mutex_lock(tsd_tsdn(tsd), &arena->large_mtx); | ||
| 699 | } | ||
| 700 | malloc_mutex_unlock(tsd_tsdn(tsd), &arena->large_mtx); | ||
| 701 | |||
| 702 | /* Bins. */ | ||
| 703 | for (unsigned i = 0; i < SC_NBINS; i++) { | ||
| 704 | for (unsigned j = 0; j < bin_infos[i].n_shards; j++) { | ||
| 705 | arena_bin_reset(tsd, arena, arena_get_bin(arena, i, j)); | ||
| 706 | } | ||
| 707 | } | ||
| 708 | pa_shard_reset(tsd_tsdn(tsd), &arena->pa_shard); | ||
| 709 | } | ||
| 710 | |||
| 711 | static void | ||
| 712 | arena_prepare_base_deletion_sync_finish(tsd_t *tsd, malloc_mutex_t **mutexes, | ||
| 713 | unsigned n_mtx) { | ||
| 714 | for (unsigned i = 0; i < n_mtx; i++) { | ||
| 715 | malloc_mutex_lock(tsd_tsdn(tsd), mutexes[i]); | ||
| 716 | malloc_mutex_unlock(tsd_tsdn(tsd), mutexes[i]); | ||
| 717 | } | ||
| 718 | } | ||
| 719 | |||
| 720 | #define ARENA_DESTROY_MAX_DELAYED_MTX 32 | ||
| 721 | static void | ||
| 722 | arena_prepare_base_deletion_sync(tsd_t *tsd, malloc_mutex_t *mtx, | ||
| 723 | malloc_mutex_t **delayed_mtx, unsigned *n_delayed) { | ||
| 724 | if (!malloc_mutex_trylock(tsd_tsdn(tsd), mtx)) { | ||
| 725 | /* No contention. */ | ||
| 726 | malloc_mutex_unlock(tsd_tsdn(tsd), mtx); | ||
| 727 | return; | ||
| 728 | } | ||
| 729 | unsigned n = *n_delayed; | ||
| 730 | assert(n < ARENA_DESTROY_MAX_DELAYED_MTX); | ||
| 731 | /* Add another to the batch. */ | ||
| 732 | delayed_mtx[n++] = mtx; | ||
| 733 | |||
| 734 | if (n == ARENA_DESTROY_MAX_DELAYED_MTX) { | ||
| 735 | arena_prepare_base_deletion_sync_finish(tsd, delayed_mtx, n); | ||
| 736 | n = 0; | ||
| 737 | } | ||
| 738 | *n_delayed = n; | ||
| 739 | } | ||
| 740 | |||
| 741 | static void | ||
| 742 | arena_prepare_base_deletion(tsd_t *tsd, base_t *base_to_destroy) { | ||
| 743 | /* | ||
| 744 | * In order to coalesce, emap_try_acquire_edata_neighbor will attempt to | ||
| 745 | * check neighbor edata's state to determine eligibility. This means | ||
| 746 | * under certain conditions, the metadata from an arena can be accessed | ||
| 747 | * w/o holding any locks from that arena. In order to guarantee safe | ||
| 748 | * memory access, the metadata and the underlying base allocator needs | ||
| 749 | * to be kept alive, until all pending accesses are done. | ||
| 750 | * | ||
| 751 | * 1) with opt_retain, the arena boundary implies the is_head state | ||
| 752 | * (tracked in the rtree leaf), and the coalesce flow will stop at the | ||
| 753 | * head state branch. Therefore no cross arena metadata access | ||
| 754 | * possible. | ||
| 755 | * | ||
| 756 | * 2) w/o opt_retain, the arena id needs to be read from the edata_t, | ||
| 757 | * meaning read only cross-arena metadata access is possible. The | ||
| 758 | * coalesce attempt will stop at the arena_id mismatch, and is always | ||
| 759 | * under one of the ecache locks. To allow safe passthrough of such | ||
| 760 | * metadata accesses, the loop below will iterate through all manual | ||
| 761 | * arenas' ecache locks. As all the metadata from this base allocator | ||
| 762 | * have been unlinked from the rtree, after going through all the | ||
| 763 | * relevant ecache locks, it's safe to say that a) pending accesses are | ||
| 764 | * all finished, and b) no new access will be generated. | ||
| 765 | */ | ||
| 766 | if (opt_retain) { | ||
| 767 | return; | ||
| 768 | } | ||
| 769 | unsigned destroy_ind = base_ind_get(base_to_destroy); | ||
| 770 | assert(destroy_ind >= manual_arena_base); | ||
| 771 | |||
| 772 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 773 | malloc_mutex_t *delayed_mtx[ARENA_DESTROY_MAX_DELAYED_MTX]; | ||
| 774 | unsigned n_delayed = 0, total = narenas_total_get(); | ||
| 775 | for (unsigned i = 0; i < total; i++) { | ||
| 776 | if (i == destroy_ind) { | ||
| 777 | continue; | ||
| 778 | } | ||
| 779 | arena_t *arena = arena_get(tsdn, i, false); | ||
| 780 | if (arena == NULL) { | ||
| 781 | continue; | ||
| 782 | } | ||
| 783 | pac_t *pac = &arena->pa_shard.pac; | ||
| 784 | arena_prepare_base_deletion_sync(tsd, &pac->ecache_dirty.mtx, | ||
| 785 | delayed_mtx, &n_delayed); | ||
| 786 | arena_prepare_base_deletion_sync(tsd, &pac->ecache_muzzy.mtx, | ||
| 787 | delayed_mtx, &n_delayed); | ||
| 788 | arena_prepare_base_deletion_sync(tsd, &pac->ecache_retained.mtx, | ||
| 789 | delayed_mtx, &n_delayed); | ||
| 790 | } | ||
| 791 | arena_prepare_base_deletion_sync_finish(tsd, delayed_mtx, n_delayed); | ||
| 792 | } | ||
| 793 | #undef ARENA_DESTROY_MAX_DELAYED_MTX | ||
| 794 | |||
| 795 | void | ||
| 796 | arena_destroy(tsd_t *tsd, arena_t *arena) { | ||
| 797 | assert(base_ind_get(arena->base) >= narenas_auto); | ||
| 798 | assert(arena_nthreads_get(arena, false) == 0); | ||
| 799 | assert(arena_nthreads_get(arena, true) == 0); | ||
| 800 | |||
| 801 | /* | ||
| 802 | * No allocations have occurred since arena_reset() was called. | ||
| 803 | * Furthermore, the caller (arena_i_destroy_ctl()) purged all cached | ||
| 804 | * extents, so only retained extents may remain and it's safe to call | ||
| 805 | * pa_shard_destroy_retained. | ||
| 806 | */ | ||
| 807 | pa_shard_destroy(tsd_tsdn(tsd), &arena->pa_shard); | ||
| 808 | |||
| 809 | /* | ||
| 810 | * Remove the arena pointer from the arenas array. We rely on the fact | ||
| 811 | * that there is no way for the application to get a dirty read from the | ||
| 812 | * arenas array unless there is an inherent race in the application | ||
| 813 | * involving access of an arena being concurrently destroyed. The | ||
| 814 | * application must synchronize knowledge of the arena's validity, so as | ||
| 815 | * long as we use an atomic write to update the arenas array, the | ||
| 816 | * application will get a clean read any time after it synchronizes | ||
| 817 | * knowledge that the arena is no longer valid. | ||
| 818 | */ | ||
| 819 | arena_set(base_ind_get(arena->base), NULL); | ||
| 820 | |||
| 821 | /* | ||
| 822 | * Destroy the base allocator, which manages all metadata ever mapped by | ||
| 823 | * this arena. The prepare function will make sure no pending access to | ||
| 824 | * the metadata in this base anymore. | ||
| 825 | */ | ||
| 826 | arena_prepare_base_deletion(tsd, arena->base); | ||
| 827 | base_delete(tsd_tsdn(tsd), arena->base); | ||
| 828 | } | ||
| 829 | |||
| 830 | static edata_t * | ||
| 831 | arena_slab_alloc(tsdn_t *tsdn, arena_t *arena, szind_t binind, unsigned binshard, | ||
| 832 | const bin_info_t *bin_info) { | ||
| 833 | bool deferred_work_generated = false; | ||
| 834 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 835 | WITNESS_RANK_CORE, 0); | ||
| 836 | |||
| 837 | bool guarded = san_slab_extent_decide_guard(tsdn, | ||
| 838 | arena_get_ehooks(arena)); | ||
| 839 | edata_t *slab = pa_alloc(tsdn, &arena->pa_shard, bin_info->slab_size, | ||
| 840 | /* alignment */ PAGE, /* slab */ true, /* szind */ binind, | ||
| 841 | /* zero */ false, guarded, &deferred_work_generated); | ||
| 842 | |||
| 843 | if (deferred_work_generated) { | ||
| 844 | arena_handle_deferred_work(tsdn, arena); | ||
| 845 | } | ||
| 846 | |||
| 847 | if (slab == NULL) { | ||
| 848 | return NULL; | ||
| 849 | } | ||
| 850 | assert(edata_slab_get(slab)); | ||
| 851 | |||
| 852 | /* Initialize slab internals. */ | ||
| 853 | slab_data_t *slab_data = edata_slab_data_get(slab); | ||
| 854 | edata_nfree_binshard_set(slab, bin_info->nregs, binshard); | ||
| 855 | bitmap_init(slab_data->bitmap, &bin_info->bitmap_info, false); | ||
| 856 | |||
| 857 | return slab; | ||
| 858 | } | ||
| 859 | |||
| 860 | /* | ||
| 861 | * Before attempting the _with_fresh_slab approaches below, the _no_fresh_slab | ||
| 862 | * variants (i.e. through slabcur and nonfull) must be tried first. | ||
| 863 | */ | ||
| 864 | static void | ||
| 865 | arena_bin_refill_slabcur_with_fresh_slab(tsdn_t *tsdn, arena_t *arena, | ||
| 866 | bin_t *bin, szind_t binind, edata_t *fresh_slab) { | ||
| 867 | malloc_mutex_assert_owner(tsdn, &bin->lock); | ||
| 868 | /* Only called after slabcur and nonfull both failed. */ | ||
| 869 | assert(bin->slabcur == NULL); | ||
| 870 | assert(edata_heap_first(&bin->slabs_nonfull) == NULL); | ||
| 871 | assert(fresh_slab != NULL); | ||
| 872 | |||
| 873 | /* A new slab from arena_slab_alloc() */ | ||
| 874 | assert(edata_nfree_get(fresh_slab) == bin_infos[binind].nregs); | ||
| 875 | if (config_stats) { | ||
| 876 | bin->stats.nslabs++; | ||
| 877 | bin->stats.curslabs++; | ||
| 878 | } | ||
| 879 | bin->slabcur = fresh_slab; | ||
| 880 | } | ||
| 881 | |||
| 882 | /* Refill slabcur and then alloc using the fresh slab */ | ||
| 883 | static void * | ||
| 884 | arena_bin_malloc_with_fresh_slab(tsdn_t *tsdn, arena_t *arena, bin_t *bin, | ||
| 885 | szind_t binind, edata_t *fresh_slab) { | ||
| 886 | malloc_mutex_assert_owner(tsdn, &bin->lock); | ||
| 887 | arena_bin_refill_slabcur_with_fresh_slab(tsdn, arena, bin, binind, | ||
| 888 | fresh_slab); | ||
| 889 | |||
| 890 | return arena_slab_reg_alloc(bin->slabcur, &bin_infos[binind]); | ||
| 891 | } | ||
| 892 | |||
| 893 | static bool | ||
| 894 | arena_bin_refill_slabcur_no_fresh_slab(tsdn_t *tsdn, arena_t *arena, | ||
| 895 | bin_t *bin) { | ||
| 896 | malloc_mutex_assert_owner(tsdn, &bin->lock); | ||
| 897 | /* Only called after arena_slab_reg_alloc[_batch] failed. */ | ||
| 898 | assert(bin->slabcur == NULL || edata_nfree_get(bin->slabcur) == 0); | ||
| 899 | |||
| 900 | if (bin->slabcur != NULL) { | ||
| 901 | arena_bin_slabs_full_insert(arena, bin, bin->slabcur); | ||
| 902 | } | ||
| 903 | |||
| 904 | /* Look for a usable slab. */ | ||
| 905 | bin->slabcur = arena_bin_slabs_nonfull_tryget(bin); | ||
| 906 | assert(bin->slabcur == NULL || edata_nfree_get(bin->slabcur) > 0); | ||
| 907 | |||
| 908 | return (bin->slabcur == NULL); | ||
| 909 | } | ||
| 910 | |||
| 911 | bin_t * | ||
| 912 | arena_bin_choose(tsdn_t *tsdn, arena_t *arena, szind_t binind, | ||
| 913 | unsigned *binshard_p) { | ||
| 914 | unsigned binshard; | ||
| 915 | if (tsdn_null(tsdn) || tsd_arena_get(tsdn_tsd(tsdn)) == NULL) { | ||
| 916 | binshard = 0; | ||
| 917 | } else { | ||
| 918 | binshard = tsd_binshardsp_get(tsdn_tsd(tsdn))->binshard[binind]; | ||
| 919 | } | ||
| 920 | assert(binshard < bin_infos[binind].n_shards); | ||
| 921 | if (binshard_p != NULL) { | ||
| 922 | *binshard_p = binshard; | ||
| 923 | } | ||
| 924 | return arena_get_bin(arena, binind, binshard); | ||
| 925 | } | ||
| 926 | |||
| 927 | void | ||
| 928 | arena_cache_bin_fill_small(tsdn_t *tsdn, arena_t *arena, | ||
| 929 | cache_bin_t *cache_bin, cache_bin_info_t *cache_bin_info, szind_t binind, | ||
| 930 | const unsigned nfill) { | ||
| 931 | assert(cache_bin_ncached_get_local(cache_bin, cache_bin_info) == 0); | ||
| 932 | |||
| 933 | const bin_info_t *bin_info = &bin_infos[binind]; | ||
| 934 | |||
| 935 | CACHE_BIN_PTR_ARRAY_DECLARE(ptrs, nfill); | ||
| 936 | cache_bin_init_ptr_array_for_fill(cache_bin, cache_bin_info, &ptrs, | ||
| 937 | nfill); | ||
| 938 | /* | ||
| 939 | * Bin-local resources are used first: 1) bin->slabcur, and 2) nonfull | ||
| 940 | * slabs. After both are exhausted, new slabs will be allocated through | ||
| 941 | * arena_slab_alloc(). | ||
| 942 | * | ||
| 943 | * Bin lock is only taken / released right before / after the while(...) | ||
| 944 | * refill loop, with new slab allocation (which has its own locking) | ||
| 945 | * kept outside of the loop. This setup facilitates flat combining, at | ||
| 946 | * the cost of the nested loop (through goto label_refill). | ||
| 947 | * | ||
| 948 | * To optimize for cases with contention and limited resources | ||
| 949 | * (e.g. hugepage-backed or non-overcommit arenas), each fill-iteration | ||
| 950 | * gets one chance of slab_alloc, and a retry of bin local resources | ||
| 951 | * after the slab allocation (regardless if slab_alloc failed, because | ||
| 952 | * the bin lock is dropped during the slab allocation). | ||
| 953 | * | ||
| 954 | * In other words, new slab allocation is allowed, as long as there was | ||
| 955 | * progress since the previous slab_alloc. This is tracked with | ||
| 956 | * made_progress below, initialized to true to jump start the first | ||
| 957 | * iteration. | ||
| 958 | * | ||
| 959 | * In other words (again), the loop will only terminate early (i.e. stop | ||
| 960 | * with filled < nfill) after going through the three steps: a) bin | ||
| 961 | * local exhausted, b) unlock and slab_alloc returns null, c) re-lock | ||
| 962 | * and bin local fails again. | ||
| 963 | */ | ||
| 964 | bool made_progress = true; | ||
| 965 | edata_t *fresh_slab = NULL; | ||
| 966 | bool alloc_and_retry = false; | ||
| 967 | unsigned filled = 0; | ||
| 968 | unsigned binshard; | ||
| 969 | bin_t *bin = arena_bin_choose(tsdn, arena, binind, &binshard); | ||
| 970 | |||
| 971 | label_refill: | ||
| 972 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 973 | |||
| 974 | while (filled < nfill) { | ||
| 975 | /* Try batch-fill from slabcur first. */ | ||
| 976 | edata_t *slabcur = bin->slabcur; | ||
| 977 | if (slabcur != NULL && edata_nfree_get(slabcur) > 0) { | ||
| 978 | unsigned tofill = nfill - filled; | ||
| 979 | unsigned nfree = edata_nfree_get(slabcur); | ||
| 980 | unsigned cnt = tofill < nfree ? tofill : nfree; | ||
| 981 | |||
| 982 | arena_slab_reg_alloc_batch(slabcur, bin_info, cnt, | ||
| 983 | &ptrs.ptr[filled]); | ||
| 984 | made_progress = true; | ||
| 985 | filled += cnt; | ||
| 986 | continue; | ||
| 987 | } | ||
| 988 | /* Next try refilling slabcur from nonfull slabs. */ | ||
| 989 | if (!arena_bin_refill_slabcur_no_fresh_slab(tsdn, arena, bin)) { | ||
| 990 | assert(bin->slabcur != NULL); | ||
| 991 | continue; | ||
| 992 | } | ||
| 993 | |||
| 994 | /* Then see if a new slab was reserved already. */ | ||
| 995 | if (fresh_slab != NULL) { | ||
| 996 | arena_bin_refill_slabcur_with_fresh_slab(tsdn, arena, | ||
| 997 | bin, binind, fresh_slab); | ||
| 998 | assert(bin->slabcur != NULL); | ||
| 999 | fresh_slab = NULL; | ||
| 1000 | continue; | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | /* Try slab_alloc if made progress (or never did slab_alloc). */ | ||
| 1004 | if (made_progress) { | ||
| 1005 | assert(bin->slabcur == NULL); | ||
| 1006 | assert(fresh_slab == NULL); | ||
| 1007 | alloc_and_retry = true; | ||
| 1008 | /* Alloc a new slab then come back. */ | ||
| 1009 | break; | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | /* OOM. */ | ||
| 1013 | |||
| 1014 | assert(fresh_slab == NULL); | ||
| 1015 | assert(!alloc_and_retry); | ||
| 1016 | break; | ||
| 1017 | } /* while (filled < nfill) loop. */ | ||
| 1018 | |||
| 1019 | if (config_stats && !alloc_and_retry) { | ||
| 1020 | bin->stats.nmalloc += filled; | ||
| 1021 | bin->stats.nrequests += cache_bin->tstats.nrequests; | ||
| 1022 | bin->stats.curregs += filled; | ||
| 1023 | bin->stats.nfills++; | ||
| 1024 | cache_bin->tstats.nrequests = 0; | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 1028 | |||
| 1029 | if (alloc_and_retry) { | ||
| 1030 | assert(fresh_slab == NULL); | ||
| 1031 | assert(filled < nfill); | ||
| 1032 | assert(made_progress); | ||
| 1033 | |||
| 1034 | fresh_slab = arena_slab_alloc(tsdn, arena, binind, binshard, | ||
| 1035 | bin_info); | ||
| 1036 | /* fresh_slab NULL case handled in the for loop. */ | ||
| 1037 | |||
| 1038 | alloc_and_retry = false; | ||
| 1039 | made_progress = false; | ||
| 1040 | goto label_refill; | ||
| 1041 | } | ||
| 1042 | assert(filled == nfill || (fresh_slab == NULL && !made_progress)); | ||
| 1043 | |||
| 1044 | /* Release if allocated but not used. */ | ||
| 1045 | if (fresh_slab != NULL) { | ||
| 1046 | assert(edata_nfree_get(fresh_slab) == bin_info->nregs); | ||
| 1047 | arena_slab_dalloc(tsdn, arena, fresh_slab); | ||
| 1048 | fresh_slab = NULL; | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | cache_bin_finish_fill(cache_bin, cache_bin_info, &ptrs, filled); | ||
| 1052 | arena_decay_tick(tsdn, arena); | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | size_t | ||
| 1056 | arena_fill_small_fresh(tsdn_t *tsdn, arena_t *arena, szind_t binind, | ||
| 1057 | void **ptrs, size_t nfill, bool zero) { | ||
| 1058 | assert(binind < SC_NBINS); | ||
| 1059 | const bin_info_t *bin_info = &bin_infos[binind]; | ||
| 1060 | const size_t nregs = bin_info->nregs; | ||
| 1061 | assert(nregs > 0); | ||
| 1062 | const size_t usize = bin_info->reg_size; | ||
| 1063 | |||
| 1064 | const bool manual_arena = !arena_is_auto(arena); | ||
| 1065 | unsigned binshard; | ||
| 1066 | bin_t *bin = arena_bin_choose(tsdn, arena, binind, &binshard); | ||
| 1067 | |||
| 1068 | size_t nslab = 0; | ||
| 1069 | size_t filled = 0; | ||
| 1070 | edata_t *slab = NULL; | ||
| 1071 | edata_list_active_t fulls; | ||
| 1072 | edata_list_active_init(&fulls); | ||
| 1073 | |||
| 1074 | while (filled < nfill && (slab = arena_slab_alloc(tsdn, arena, binind, | ||
| 1075 | binshard, bin_info)) != NULL) { | ||
| 1076 | assert((size_t)edata_nfree_get(slab) == nregs); | ||
| 1077 | ++nslab; | ||
| 1078 | size_t batch = nfill - filled; | ||
| 1079 | if (batch > nregs) { | ||
| 1080 | batch = nregs; | ||
| 1081 | } | ||
| 1082 | assert(batch > 0); | ||
| 1083 | arena_slab_reg_alloc_batch(slab, bin_info, (unsigned)batch, | ||
| 1084 | &ptrs[filled]); | ||
| 1085 | assert(edata_addr_get(slab) == ptrs[filled]); | ||
| 1086 | if (zero) { | ||
| 1087 | memset(ptrs[filled], 0, batch * usize); | ||
| 1088 | } | ||
| 1089 | filled += batch; | ||
| 1090 | if (batch == nregs) { | ||
| 1091 | if (manual_arena) { | ||
| 1092 | edata_list_active_append(&fulls, slab); | ||
| 1093 | } | ||
| 1094 | slab = NULL; | ||
| 1095 | } | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 1099 | /* | ||
| 1100 | * Only the last slab can be non-empty, and the last slab is non-empty | ||
| 1101 | * iff slab != NULL. | ||
| 1102 | */ | ||
| 1103 | if (slab != NULL) { | ||
| 1104 | arena_bin_lower_slab(tsdn, arena, slab, bin); | ||
| 1105 | } | ||
| 1106 | if (manual_arena) { | ||
| 1107 | edata_list_active_concat(&bin->slabs_full, &fulls); | ||
| 1108 | } | ||
| 1109 | assert(edata_list_active_empty(&fulls)); | ||
| 1110 | if (config_stats) { | ||
| 1111 | bin->stats.nslabs += nslab; | ||
| 1112 | bin->stats.curslabs += nslab; | ||
| 1113 | bin->stats.nmalloc += filled; | ||
| 1114 | bin->stats.nrequests += filled; | ||
| 1115 | bin->stats.curregs += filled; | ||
| 1116 | } | ||
| 1117 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 1118 | |||
| 1119 | arena_decay_tick(tsdn, arena); | ||
| 1120 | return filled; | ||
| 1121 | } | ||
| 1122 | |||
| 1123 | /* | ||
| 1124 | * Without allocating a new slab, try arena_slab_reg_alloc() and re-fill | ||
| 1125 | * bin->slabcur if necessary. | ||
| 1126 | */ | ||
| 1127 | static void * | ||
| 1128 | arena_bin_malloc_no_fresh_slab(tsdn_t *tsdn, arena_t *arena, bin_t *bin, | ||
| 1129 | szind_t binind) { | ||
| 1130 | malloc_mutex_assert_owner(tsdn, &bin->lock); | ||
| 1131 | if (bin->slabcur == NULL || edata_nfree_get(bin->slabcur) == 0) { | ||
| 1132 | if (arena_bin_refill_slabcur_no_fresh_slab(tsdn, arena, bin)) { | ||
| 1133 | return NULL; | ||
| 1134 | } | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | assert(bin->slabcur != NULL && edata_nfree_get(bin->slabcur) > 0); | ||
| 1138 | return arena_slab_reg_alloc(bin->slabcur, &bin_infos[binind]); | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | static void * | ||
| 1142 | arena_malloc_small(tsdn_t *tsdn, arena_t *arena, szind_t binind, bool zero) { | ||
| 1143 | assert(binind < SC_NBINS); | ||
| 1144 | const bin_info_t *bin_info = &bin_infos[binind]; | ||
| 1145 | size_t usize = sz_index2size(binind); | ||
| 1146 | unsigned binshard; | ||
| 1147 | bin_t *bin = arena_bin_choose(tsdn, arena, binind, &binshard); | ||
| 1148 | |||
| 1149 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 1150 | edata_t *fresh_slab = NULL; | ||
| 1151 | void *ret = arena_bin_malloc_no_fresh_slab(tsdn, arena, bin, binind); | ||
| 1152 | if (ret == NULL) { | ||
| 1153 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 1154 | /******************************/ | ||
| 1155 | fresh_slab = arena_slab_alloc(tsdn, arena, binind, binshard, | ||
| 1156 | bin_info); | ||
| 1157 | /********************************/ | ||
| 1158 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 1159 | /* Retry since the lock was dropped. */ | ||
| 1160 | ret = arena_bin_malloc_no_fresh_slab(tsdn, arena, bin, binind); | ||
| 1161 | if (ret == NULL) { | ||
| 1162 | if (fresh_slab == NULL) { | ||
| 1163 | /* OOM */ | ||
| 1164 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 1165 | return NULL; | ||
| 1166 | } | ||
| 1167 | ret = arena_bin_malloc_with_fresh_slab(tsdn, arena, bin, | ||
| 1168 | binind, fresh_slab); | ||
| 1169 | fresh_slab = NULL; | ||
| 1170 | } | ||
| 1171 | } | ||
| 1172 | if (config_stats) { | ||
| 1173 | bin->stats.nmalloc++; | ||
| 1174 | bin->stats.nrequests++; | ||
| 1175 | bin->stats.curregs++; | ||
| 1176 | } | ||
| 1177 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 1178 | |||
| 1179 | if (fresh_slab != NULL) { | ||
| 1180 | arena_slab_dalloc(tsdn, arena, fresh_slab); | ||
| 1181 | } | ||
| 1182 | if (zero) { | ||
| 1183 | memset(ret, 0, usize); | ||
| 1184 | } | ||
| 1185 | arena_decay_tick(tsdn, arena); | ||
| 1186 | |||
| 1187 | return ret; | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | void * | ||
| 1191 | arena_malloc_hard(tsdn_t *tsdn, arena_t *arena, size_t size, szind_t ind, | ||
| 1192 | bool zero) { | ||
| 1193 | assert(!tsdn_null(tsdn) || arena != NULL); | ||
| 1194 | |||
| 1195 | if (likely(!tsdn_null(tsdn))) { | ||
| 1196 | arena = arena_choose_maybe_huge(tsdn_tsd(tsdn), arena, size); | ||
| 1197 | } | ||
| 1198 | if (unlikely(arena == NULL)) { | ||
| 1199 | return NULL; | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | if (likely(size <= SC_SMALL_MAXCLASS)) { | ||
| 1203 | return arena_malloc_small(tsdn, arena, ind, zero); | ||
| 1204 | } | ||
| 1205 | return large_malloc(tsdn, arena, sz_index2size(ind), zero); | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | void * | ||
| 1209 | arena_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, | ||
| 1210 | bool zero, tcache_t *tcache) { | ||
| 1211 | void *ret; | ||
| 1212 | |||
| 1213 | if (usize <= SC_SMALL_MAXCLASS) { | ||
| 1214 | /* Small; alignment doesn't require special slab placement. */ | ||
| 1215 | |||
| 1216 | /* usize should be a result of sz_sa2u() */ | ||
| 1217 | assert((usize & (alignment - 1)) == 0); | ||
| 1218 | |||
| 1219 | /* | ||
| 1220 | * Small usize can't come from an alignment larger than a page. | ||
| 1221 | */ | ||
| 1222 | assert(alignment <= PAGE); | ||
| 1223 | |||
| 1224 | ret = arena_malloc(tsdn, arena, usize, sz_size2index(usize), | ||
| 1225 | zero, tcache, true); | ||
| 1226 | } else { | ||
| 1227 | if (likely(alignment <= CACHELINE)) { | ||
| 1228 | ret = large_malloc(tsdn, arena, usize, zero); | ||
| 1229 | } else { | ||
| 1230 | ret = large_palloc(tsdn, arena, usize, alignment, zero); | ||
| 1231 | } | ||
| 1232 | } | ||
| 1233 | return ret; | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | void | ||
| 1237 | arena_prof_promote(tsdn_t *tsdn, void *ptr, size_t usize) { | ||
| 1238 | cassert(config_prof); | ||
| 1239 | assert(ptr != NULL); | ||
| 1240 | assert(isalloc(tsdn, ptr) == SC_LARGE_MINCLASS); | ||
| 1241 | assert(usize <= SC_SMALL_MAXCLASS); | ||
| 1242 | |||
| 1243 | if (config_opt_safety_checks) { | ||
| 1244 | safety_check_set_redzone(ptr, usize, SC_LARGE_MINCLASS); | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 1248 | |||
| 1249 | szind_t szind = sz_size2index(usize); | ||
| 1250 | edata_szind_set(edata, szind); | ||
| 1251 | emap_remap(tsdn, &arena_emap_global, edata, szind, /* slab */ false); | ||
| 1252 | |||
| 1253 | assert(isalloc(tsdn, ptr) == usize); | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | static size_t | ||
| 1257 | arena_prof_demote(tsdn_t *tsdn, edata_t *edata, const void *ptr) { | ||
| 1258 | cassert(config_prof); | ||
| 1259 | assert(ptr != NULL); | ||
| 1260 | |||
| 1261 | edata_szind_set(edata, SC_NBINS); | ||
| 1262 | emap_remap(tsdn, &arena_emap_global, edata, SC_NBINS, /* slab */ false); | ||
| 1263 | |||
| 1264 | assert(isalloc(tsdn, ptr) == SC_LARGE_MINCLASS); | ||
| 1265 | |||
| 1266 | return SC_LARGE_MINCLASS; | ||
| 1267 | } | ||
| 1268 | |||
| 1269 | void | ||
| 1270 | arena_dalloc_promoted(tsdn_t *tsdn, void *ptr, tcache_t *tcache, | ||
| 1271 | bool slow_path) { | ||
| 1272 | cassert(config_prof); | ||
| 1273 | assert(opt_prof); | ||
| 1274 | |||
| 1275 | edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 1276 | size_t usize = edata_usize_get(edata); | ||
| 1277 | size_t bumped_usize = arena_prof_demote(tsdn, edata, ptr); | ||
| 1278 | if (config_opt_safety_checks && usize < SC_LARGE_MINCLASS) { | ||
| 1279 | /* | ||
| 1280 | * Currently, we only do redzoning for small sampled | ||
| 1281 | * allocations. | ||
| 1282 | */ | ||
| 1283 | assert(bumped_usize == SC_LARGE_MINCLASS); | ||
| 1284 | safety_check_verify_redzone(ptr, usize, bumped_usize); | ||
| 1285 | } | ||
| 1286 | if (bumped_usize <= tcache_maxclass && tcache != NULL) { | ||
| 1287 | tcache_dalloc_large(tsdn_tsd(tsdn), tcache, ptr, | ||
| 1288 | sz_size2index(bumped_usize), slow_path); | ||
| 1289 | } else { | ||
| 1290 | large_dalloc(tsdn, edata); | ||
| 1291 | } | ||
| 1292 | } | ||
| 1293 | |||
| 1294 | static void | ||
| 1295 | arena_dissociate_bin_slab(arena_t *arena, edata_t *slab, bin_t *bin) { | ||
| 1296 | /* Dissociate slab from bin. */ | ||
| 1297 | if (slab == bin->slabcur) { | ||
| 1298 | bin->slabcur = NULL; | ||
| 1299 | } else { | ||
| 1300 | szind_t binind = edata_szind_get(slab); | ||
| 1301 | const bin_info_t *bin_info = &bin_infos[binind]; | ||
| 1302 | |||
| 1303 | /* | ||
| 1304 | * The following block's conditional is necessary because if the | ||
| 1305 | * slab only contains one region, then it never gets inserted | ||
| 1306 | * into the non-full slabs heap. | ||
| 1307 | */ | ||
| 1308 | if (bin_info->nregs == 1) { | ||
| 1309 | arena_bin_slabs_full_remove(arena, bin, slab); | ||
| 1310 | } else { | ||
| 1311 | arena_bin_slabs_nonfull_remove(bin, slab); | ||
| 1312 | } | ||
| 1313 | } | ||
| 1314 | } | ||
| 1315 | |||
| 1316 | static void | ||
| 1317 | arena_bin_lower_slab(tsdn_t *tsdn, arena_t *arena, edata_t *slab, | ||
| 1318 | bin_t *bin) { | ||
| 1319 | assert(edata_nfree_get(slab) > 0); | ||
| 1320 | |||
| 1321 | /* | ||
| 1322 | * Make sure that if bin->slabcur is non-NULL, it refers to the | ||
| 1323 | * oldest/lowest non-full slab. It is okay to NULL slabcur out rather | ||
| 1324 | * than proactively keeping it pointing at the oldest/lowest non-full | ||
| 1325 | * slab. | ||
| 1326 | */ | ||
| 1327 | if (bin->slabcur != NULL && edata_snad_comp(bin->slabcur, slab) > 0) { | ||
| 1328 | /* Switch slabcur. */ | ||
| 1329 | if (edata_nfree_get(bin->slabcur) > 0) { | ||
| 1330 | arena_bin_slabs_nonfull_insert(bin, bin->slabcur); | ||
| 1331 | } else { | ||
| 1332 | arena_bin_slabs_full_insert(arena, bin, bin->slabcur); | ||
| 1333 | } | ||
| 1334 | bin->slabcur = slab; | ||
| 1335 | if (config_stats) { | ||
| 1336 | bin->stats.reslabs++; | ||
| 1337 | } | ||
| 1338 | } else { | ||
| 1339 | arena_bin_slabs_nonfull_insert(bin, slab); | ||
| 1340 | } | ||
| 1341 | } | ||
| 1342 | |||
| 1343 | static void | ||
| 1344 | arena_dalloc_bin_slab_prepare(tsdn_t *tsdn, edata_t *slab, bin_t *bin) { | ||
| 1345 | malloc_mutex_assert_owner(tsdn, &bin->lock); | ||
| 1346 | |||
| 1347 | assert(slab != bin->slabcur); | ||
| 1348 | if (config_stats) { | ||
| 1349 | bin->stats.curslabs--; | ||
| 1350 | } | ||
| 1351 | } | ||
| 1352 | |||
| 1353 | void | ||
| 1354 | arena_dalloc_bin_locked_handle_newly_empty(tsdn_t *tsdn, arena_t *arena, | ||
| 1355 | edata_t *slab, bin_t *bin) { | ||
| 1356 | arena_dissociate_bin_slab(arena, slab, bin); | ||
| 1357 | arena_dalloc_bin_slab_prepare(tsdn, slab, bin); | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | void | ||
| 1361 | arena_dalloc_bin_locked_handle_newly_nonempty(tsdn_t *tsdn, arena_t *arena, | ||
| 1362 | edata_t *slab, bin_t *bin) { | ||
| 1363 | arena_bin_slabs_full_remove(arena, bin, slab); | ||
| 1364 | arena_bin_lower_slab(tsdn, arena, slab, bin); | ||
| 1365 | } | ||
| 1366 | |||
| 1367 | static void | ||
| 1368 | arena_dalloc_bin(tsdn_t *tsdn, arena_t *arena, edata_t *edata, void *ptr) { | ||
| 1369 | szind_t binind = edata_szind_get(edata); | ||
| 1370 | unsigned binshard = edata_binshard_get(edata); | ||
| 1371 | bin_t *bin = arena_get_bin(arena, binind, binshard); | ||
| 1372 | |||
| 1373 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 1374 | arena_dalloc_bin_locked_info_t info; | ||
| 1375 | arena_dalloc_bin_locked_begin(&info, binind); | ||
| 1376 | bool ret = arena_dalloc_bin_locked_step(tsdn, arena, bin, | ||
| 1377 | &info, binind, edata, ptr); | ||
| 1378 | arena_dalloc_bin_locked_finish(tsdn, arena, bin, &info); | ||
| 1379 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 1380 | |||
| 1381 | if (ret) { | ||
| 1382 | arena_slab_dalloc(tsdn, arena, edata); | ||
| 1383 | } | ||
| 1384 | } | ||
| 1385 | |||
| 1386 | void | ||
| 1387 | arena_dalloc_small(tsdn_t *tsdn, void *ptr) { | ||
| 1388 | edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 1389 | arena_t *arena = arena_get_from_edata(edata); | ||
| 1390 | |||
| 1391 | arena_dalloc_bin(tsdn, arena, edata, ptr); | ||
| 1392 | arena_decay_tick(tsdn, arena); | ||
| 1393 | } | ||
| 1394 | |||
| 1395 | bool | ||
| 1396 | arena_ralloc_no_move(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, | ||
| 1397 | size_t extra, bool zero, size_t *newsize) { | ||
| 1398 | bool ret; | ||
| 1399 | /* Calls with non-zero extra had to clamp extra. */ | ||
| 1400 | assert(extra == 0 || size + extra <= SC_LARGE_MAXCLASS); | ||
| 1401 | |||
| 1402 | edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 1403 | if (unlikely(size > SC_LARGE_MAXCLASS)) { | ||
| 1404 | ret = true; | ||
| 1405 | goto done; | ||
| 1406 | } | ||
| 1407 | |||
| 1408 | size_t usize_min = sz_s2u(size); | ||
| 1409 | size_t usize_max = sz_s2u(size + extra); | ||
| 1410 | if (likely(oldsize <= SC_SMALL_MAXCLASS && usize_min | ||
| 1411 | <= SC_SMALL_MAXCLASS)) { | ||
| 1412 | /* | ||
| 1413 | * Avoid moving the allocation if the size class can be left the | ||
| 1414 | * same. | ||
| 1415 | */ | ||
| 1416 | assert(bin_infos[sz_size2index(oldsize)].reg_size == | ||
| 1417 | oldsize); | ||
| 1418 | if ((usize_max > SC_SMALL_MAXCLASS | ||
| 1419 | || sz_size2index(usize_max) != sz_size2index(oldsize)) | ||
| 1420 | && (size > oldsize || usize_max < oldsize)) { | ||
| 1421 | ret = true; | ||
| 1422 | goto done; | ||
| 1423 | } | ||
| 1424 | |||
| 1425 | arena_t *arena = arena_get_from_edata(edata); | ||
| 1426 | arena_decay_tick(tsdn, arena); | ||
| 1427 | ret = false; | ||
| 1428 | } else if (oldsize >= SC_LARGE_MINCLASS | ||
| 1429 | && usize_max >= SC_LARGE_MINCLASS) { | ||
| 1430 | ret = large_ralloc_no_move(tsdn, edata, usize_min, usize_max, | ||
| 1431 | zero); | ||
| 1432 | } else { | ||
| 1433 | ret = true; | ||
| 1434 | } | ||
| 1435 | done: | ||
| 1436 | assert(edata == emap_edata_lookup(tsdn, &arena_emap_global, ptr)); | ||
| 1437 | *newsize = edata_usize_get(edata); | ||
| 1438 | |||
| 1439 | return ret; | ||
| 1440 | } | ||
| 1441 | |||
| 1442 | static void * | ||
| 1443 | arena_ralloc_move_helper(tsdn_t *tsdn, arena_t *arena, size_t usize, | ||
| 1444 | size_t alignment, bool zero, tcache_t *tcache) { | ||
| 1445 | if (alignment == 0) { | ||
| 1446 | return arena_malloc(tsdn, arena, usize, sz_size2index(usize), | ||
| 1447 | zero, tcache, true); | ||
| 1448 | } | ||
| 1449 | usize = sz_sa2u(usize, alignment); | ||
| 1450 | if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { | ||
| 1451 | return NULL; | ||
| 1452 | } | ||
| 1453 | return ipalloct(tsdn, usize, alignment, zero, tcache, arena); | ||
| 1454 | } | ||
| 1455 | |||
| 1456 | void * | ||
| 1457 | arena_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t oldsize, | ||
| 1458 | size_t size, size_t alignment, bool zero, tcache_t *tcache, | ||
| 1459 | hook_ralloc_args_t *hook_args) { | ||
| 1460 | size_t usize = alignment == 0 ? sz_s2u(size) : sz_sa2u(size, alignment); | ||
| 1461 | if (unlikely(usize == 0 || size > SC_LARGE_MAXCLASS)) { | ||
| 1462 | return NULL; | ||
| 1463 | } | ||
| 1464 | |||
| 1465 | if (likely(usize <= SC_SMALL_MAXCLASS)) { | ||
| 1466 | /* Try to avoid moving the allocation. */ | ||
| 1467 | UNUSED size_t newsize; | ||
| 1468 | if (!arena_ralloc_no_move(tsdn, ptr, oldsize, usize, 0, zero, | ||
| 1469 | &newsize)) { | ||
| 1470 | hook_invoke_expand(hook_args->is_realloc | ||
| 1471 | ? hook_expand_realloc : hook_expand_rallocx, | ||
| 1472 | ptr, oldsize, usize, (uintptr_t)ptr, | ||
| 1473 | hook_args->args); | ||
| 1474 | return ptr; | ||
| 1475 | } | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | if (oldsize >= SC_LARGE_MINCLASS | ||
| 1479 | && usize >= SC_LARGE_MINCLASS) { | ||
| 1480 | return large_ralloc(tsdn, arena, ptr, usize, | ||
| 1481 | alignment, zero, tcache, hook_args); | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | /* | ||
| 1485 | * size and oldsize are different enough that we need to move the | ||
| 1486 | * object. In that case, fall back to allocating new space and copying. | ||
| 1487 | */ | ||
| 1488 | void *ret = arena_ralloc_move_helper(tsdn, arena, usize, alignment, | ||
| 1489 | zero, tcache); | ||
| 1490 | if (ret == NULL) { | ||
| 1491 | return NULL; | ||
| 1492 | } | ||
| 1493 | |||
| 1494 | hook_invoke_alloc(hook_args->is_realloc | ||
| 1495 | ? hook_alloc_realloc : hook_alloc_rallocx, ret, (uintptr_t)ret, | ||
| 1496 | hook_args->args); | ||
| 1497 | hook_invoke_dalloc(hook_args->is_realloc | ||
| 1498 | ? hook_dalloc_realloc : hook_dalloc_rallocx, ptr, hook_args->args); | ||
| 1499 | |||
| 1500 | /* | ||
| 1501 | * Junk/zero-filling were already done by | ||
| 1502 | * ipalloc()/arena_malloc(). | ||
| 1503 | */ | ||
| 1504 | size_t copysize = (usize < oldsize) ? usize : oldsize; | ||
| 1505 | memcpy(ret, ptr, copysize); | ||
| 1506 | isdalloct(tsdn, ptr, oldsize, tcache, NULL, true); | ||
| 1507 | return ret; | ||
| 1508 | } | ||
| 1509 | |||
| 1510 | ehooks_t * | ||
| 1511 | arena_get_ehooks(arena_t *arena) { | ||
| 1512 | return base_ehooks_get(arena->base); | ||
| 1513 | } | ||
| 1514 | |||
| 1515 | extent_hooks_t * | ||
| 1516 | arena_set_extent_hooks(tsd_t *tsd, arena_t *arena, | ||
| 1517 | extent_hooks_t *extent_hooks) { | ||
| 1518 | background_thread_info_t *info; | ||
| 1519 | if (have_background_thread) { | ||
| 1520 | info = arena_background_thread_info_get(arena); | ||
| 1521 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 1522 | } | ||
| 1523 | /* No using the HPA now that we have the custom hooks. */ | ||
| 1524 | pa_shard_disable_hpa(tsd_tsdn(tsd), &arena->pa_shard); | ||
| 1525 | extent_hooks_t *ret = base_extent_hooks_set(arena->base, extent_hooks); | ||
| 1526 | if (have_background_thread) { | ||
| 1527 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 1528 | } | ||
| 1529 | |||
| 1530 | return ret; | ||
| 1531 | } | ||
| 1532 | |||
| 1533 | dss_prec_t | ||
| 1534 | arena_dss_prec_get(arena_t *arena) { | ||
| 1535 | return (dss_prec_t)atomic_load_u(&arena->dss_prec, ATOMIC_ACQUIRE); | ||
| 1536 | } | ||
| 1537 | |||
| 1538 | bool | ||
| 1539 | arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec) { | ||
| 1540 | if (!have_dss) { | ||
| 1541 | return (dss_prec != dss_prec_disabled); | ||
| 1542 | } | ||
| 1543 | atomic_store_u(&arena->dss_prec, (unsigned)dss_prec, ATOMIC_RELEASE); | ||
| 1544 | return false; | ||
| 1545 | } | ||
| 1546 | |||
| 1547 | ssize_t | ||
| 1548 | arena_dirty_decay_ms_default_get(void) { | ||
| 1549 | return atomic_load_zd(&dirty_decay_ms_default, ATOMIC_RELAXED); | ||
| 1550 | } | ||
| 1551 | |||
| 1552 | bool | ||
| 1553 | arena_dirty_decay_ms_default_set(ssize_t decay_ms) { | ||
| 1554 | if (!decay_ms_valid(decay_ms)) { | ||
| 1555 | return true; | ||
| 1556 | } | ||
| 1557 | atomic_store_zd(&dirty_decay_ms_default, decay_ms, ATOMIC_RELAXED); | ||
| 1558 | return false; | ||
| 1559 | } | ||
| 1560 | |||
| 1561 | ssize_t | ||
| 1562 | arena_muzzy_decay_ms_default_get(void) { | ||
| 1563 | return atomic_load_zd(&muzzy_decay_ms_default, ATOMIC_RELAXED); | ||
| 1564 | } | ||
| 1565 | |||
| 1566 | bool | ||
| 1567 | arena_muzzy_decay_ms_default_set(ssize_t decay_ms) { | ||
| 1568 | if (!decay_ms_valid(decay_ms)) { | ||
| 1569 | return true; | ||
| 1570 | } | ||
| 1571 | atomic_store_zd(&muzzy_decay_ms_default, decay_ms, ATOMIC_RELAXED); | ||
| 1572 | return false; | ||
| 1573 | } | ||
| 1574 | |||
| 1575 | bool | ||
| 1576 | arena_retain_grow_limit_get_set(tsd_t *tsd, arena_t *arena, size_t *old_limit, | ||
| 1577 | size_t *new_limit) { | ||
| 1578 | assert(opt_retain); | ||
| 1579 | return pac_retain_grow_limit_get_set(tsd_tsdn(tsd), | ||
| 1580 | &arena->pa_shard.pac, old_limit, new_limit); | ||
| 1581 | } | ||
| 1582 | |||
| 1583 | unsigned | ||
| 1584 | arena_nthreads_get(arena_t *arena, bool internal) { | ||
| 1585 | return atomic_load_u(&arena->nthreads[internal], ATOMIC_RELAXED); | ||
| 1586 | } | ||
| 1587 | |||
| 1588 | void | ||
| 1589 | arena_nthreads_inc(arena_t *arena, bool internal) { | ||
| 1590 | atomic_fetch_add_u(&arena->nthreads[internal], 1, ATOMIC_RELAXED); | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | void | ||
| 1594 | arena_nthreads_dec(arena_t *arena, bool internal) { | ||
| 1595 | atomic_fetch_sub_u(&arena->nthreads[internal], 1, ATOMIC_RELAXED); | ||
| 1596 | } | ||
| 1597 | |||
| 1598 | arena_t * | ||
| 1599 | arena_new(tsdn_t *tsdn, unsigned ind, const arena_config_t *config) { | ||
| 1600 | arena_t *arena; | ||
| 1601 | base_t *base; | ||
| 1602 | unsigned i; | ||
| 1603 | |||
| 1604 | if (ind == 0) { | ||
| 1605 | base = b0get(); | ||
| 1606 | } else { | ||
| 1607 | base = base_new(tsdn, ind, config->extent_hooks, | ||
| 1608 | config->metadata_use_hooks); | ||
| 1609 | if (base == NULL) { | ||
| 1610 | return NULL; | ||
| 1611 | } | ||
| 1612 | } | ||
| 1613 | |||
| 1614 | size_t arena_size = sizeof(arena_t) + sizeof(bin_t) * nbins_total; | ||
| 1615 | arena = (arena_t *)base_alloc(tsdn, base, arena_size, CACHELINE); | ||
| 1616 | if (arena == NULL) { | ||
| 1617 | goto label_error; | ||
| 1618 | } | ||
| 1619 | |||
| 1620 | atomic_store_u(&arena->nthreads[0], 0, ATOMIC_RELAXED); | ||
| 1621 | atomic_store_u(&arena->nthreads[1], 0, ATOMIC_RELAXED); | ||
| 1622 | arena->last_thd = NULL; | ||
| 1623 | |||
| 1624 | if (config_stats) { | ||
| 1625 | if (arena_stats_init(tsdn, &arena->stats)) { | ||
| 1626 | goto label_error; | ||
| 1627 | } | ||
| 1628 | |||
| 1629 | ql_new(&arena->tcache_ql); | ||
| 1630 | ql_new(&arena->cache_bin_array_descriptor_ql); | ||
| 1631 | if (malloc_mutex_init(&arena->tcache_ql_mtx, "tcache_ql", | ||
| 1632 | WITNESS_RANK_TCACHE_QL, malloc_mutex_rank_exclusive)) { | ||
| 1633 | goto label_error; | ||
| 1634 | } | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | atomic_store_u(&arena->dss_prec, (unsigned)extent_dss_prec_get(), | ||
| 1638 | ATOMIC_RELAXED); | ||
| 1639 | |||
| 1640 | edata_list_active_init(&arena->large); | ||
| 1641 | if (malloc_mutex_init(&arena->large_mtx, "arena_large", | ||
| 1642 | WITNESS_RANK_ARENA_LARGE, malloc_mutex_rank_exclusive)) { | ||
| 1643 | goto label_error; | ||
| 1644 | } | ||
| 1645 | |||
| 1646 | nstime_t cur_time; | ||
| 1647 | nstime_init_update(&cur_time); | ||
| 1648 | if (pa_shard_init(tsdn, &arena->pa_shard, &arena_pa_central_global, | ||
| 1649 | &arena_emap_global, base, ind, &arena->stats.pa_shard_stats, | ||
| 1650 | LOCKEDINT_MTX(arena->stats.mtx), &cur_time, oversize_threshold, | ||
| 1651 | arena_dirty_decay_ms_default_get(), | ||
| 1652 | arena_muzzy_decay_ms_default_get())) { | ||
| 1653 | goto label_error; | ||
| 1654 | } | ||
| 1655 | |||
| 1656 | /* Initialize bins. */ | ||
| 1657 | atomic_store_u(&arena->binshard_next, 0, ATOMIC_RELEASE); | ||
| 1658 | for (i = 0; i < nbins_total; i++) { | ||
| 1659 | bool err = bin_init(&arena->bins[i]); | ||
| 1660 | if (err) { | ||
| 1661 | goto label_error; | ||
| 1662 | } | ||
| 1663 | } | ||
| 1664 | |||
| 1665 | arena->base = base; | ||
| 1666 | /* Set arena before creating background threads. */ | ||
| 1667 | arena_set(ind, arena); | ||
| 1668 | arena->ind = ind; | ||
| 1669 | |||
| 1670 | nstime_init_update(&arena->create_time); | ||
| 1671 | |||
| 1672 | /* | ||
| 1673 | * We turn on the HPA if set to. There are two exceptions: | ||
| 1674 | * - Custom extent hooks (we should only return memory allocated from | ||
| 1675 | * them in that case). | ||
| 1676 | * - Arena 0 initialization. In this case, we're mid-bootstrapping, and | ||
| 1677 | * so arena_hpa_global is not yet initialized. | ||
| 1678 | */ | ||
| 1679 | if (opt_hpa && ehooks_are_default(base_ehooks_get(base)) && ind != 0) { | ||
| 1680 | hpa_shard_opts_t hpa_shard_opts = opt_hpa_opts; | ||
| 1681 | hpa_shard_opts.deferral_allowed = background_thread_enabled(); | ||
| 1682 | if (pa_shard_enable_hpa(tsdn, &arena->pa_shard, | ||
| 1683 | &hpa_shard_opts, &opt_hpa_sec_opts)) { | ||
| 1684 | goto label_error; | ||
| 1685 | } | ||
| 1686 | } | ||
| 1687 | |||
| 1688 | /* We don't support reentrancy for arena 0 bootstrapping. */ | ||
| 1689 | if (ind != 0) { | ||
| 1690 | /* | ||
| 1691 | * If we're here, then arena 0 already exists, so bootstrapping | ||
| 1692 | * is done enough that we should have tsd. | ||
| 1693 | */ | ||
| 1694 | assert(!tsdn_null(tsdn)); | ||
| 1695 | pre_reentrancy(tsdn_tsd(tsdn), arena); | ||
| 1696 | if (test_hooks_arena_new_hook) { | ||
| 1697 | test_hooks_arena_new_hook(); | ||
| 1698 | } | ||
| 1699 | post_reentrancy(tsdn_tsd(tsdn)); | ||
| 1700 | } | ||
| 1701 | |||
| 1702 | return arena; | ||
| 1703 | label_error: | ||
| 1704 | if (ind != 0) { | ||
| 1705 | base_delete(tsdn, base); | ||
| 1706 | } | ||
| 1707 | return NULL; | ||
| 1708 | } | ||
| 1709 | |||
| 1710 | arena_t * | ||
| 1711 | arena_choose_huge(tsd_t *tsd) { | ||
| 1712 | /* huge_arena_ind can be 0 during init (will use a0). */ | ||
| 1713 | if (huge_arena_ind == 0) { | ||
| 1714 | assert(!malloc_initialized()); | ||
| 1715 | } | ||
| 1716 | |||
| 1717 | arena_t *huge_arena = arena_get(tsd_tsdn(tsd), huge_arena_ind, false); | ||
| 1718 | if (huge_arena == NULL) { | ||
| 1719 | /* Create the huge arena on demand. */ | ||
| 1720 | assert(huge_arena_ind != 0); | ||
| 1721 | huge_arena = arena_get(tsd_tsdn(tsd), huge_arena_ind, true); | ||
| 1722 | if (huge_arena == NULL) { | ||
| 1723 | return NULL; | ||
| 1724 | } | ||
| 1725 | /* | ||
| 1726 | * Purge eagerly for huge allocations, because: 1) number of | ||
| 1727 | * huge allocations is usually small, which means ticker based | ||
| 1728 | * decay is not reliable; and 2) less immediate reuse is | ||
| 1729 | * expected for huge allocations. | ||
| 1730 | */ | ||
| 1731 | if (arena_dirty_decay_ms_default_get() > 0) { | ||
| 1732 | arena_decay_ms_set(tsd_tsdn(tsd), huge_arena, | ||
| 1733 | extent_state_dirty, 0); | ||
| 1734 | } | ||
| 1735 | if (arena_muzzy_decay_ms_default_get() > 0) { | ||
| 1736 | arena_decay_ms_set(tsd_tsdn(tsd), huge_arena, | ||
| 1737 | extent_state_muzzy, 0); | ||
| 1738 | } | ||
| 1739 | } | ||
| 1740 | |||
| 1741 | return huge_arena; | ||
| 1742 | } | ||
| 1743 | |||
| 1744 | bool | ||
| 1745 | arena_init_huge(void) { | ||
| 1746 | bool huge_enabled; | ||
| 1747 | |||
| 1748 | /* The threshold should be large size class. */ | ||
| 1749 | if (opt_oversize_threshold > SC_LARGE_MAXCLASS || | ||
| 1750 | opt_oversize_threshold < SC_LARGE_MINCLASS) { | ||
| 1751 | opt_oversize_threshold = 0; | ||
| 1752 | oversize_threshold = SC_LARGE_MAXCLASS + PAGE; | ||
| 1753 | huge_enabled = false; | ||
| 1754 | } else { | ||
| 1755 | /* Reserve the index for the huge arena. */ | ||
| 1756 | huge_arena_ind = narenas_total_get(); | ||
| 1757 | oversize_threshold = opt_oversize_threshold; | ||
| 1758 | huge_enabled = true; | ||
| 1759 | } | ||
| 1760 | |||
| 1761 | return huge_enabled; | ||
| 1762 | } | ||
| 1763 | |||
| 1764 | bool | ||
| 1765 | arena_is_huge(unsigned arena_ind) { | ||
| 1766 | if (huge_arena_ind == 0) { | ||
| 1767 | return false; | ||
| 1768 | } | ||
| 1769 | return (arena_ind == huge_arena_ind); | ||
| 1770 | } | ||
| 1771 | |||
| 1772 | bool | ||
| 1773 | arena_boot(sc_data_t *sc_data, base_t *base, bool hpa) { | ||
| 1774 | arena_dirty_decay_ms_default_set(opt_dirty_decay_ms); | ||
| 1775 | arena_muzzy_decay_ms_default_set(opt_muzzy_decay_ms); | ||
| 1776 | for (unsigned i = 0; i < SC_NBINS; i++) { | ||
| 1777 | sc_t *sc = &sc_data->sc[i]; | ||
| 1778 | div_init(&arena_binind_div_info[i], | ||
| 1779 | (1U << sc->lg_base) + (sc->ndelta << sc->lg_delta)); | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | uint32_t cur_offset = (uint32_t)offsetof(arena_t, bins); | ||
| 1783 | for (szind_t i = 0; i < SC_NBINS; i++) { | ||
| 1784 | arena_bin_offsets[i] = cur_offset; | ||
| 1785 | nbins_total += bin_infos[i].n_shards; | ||
| 1786 | cur_offset += (uint32_t)(bin_infos[i].n_shards * sizeof(bin_t)); | ||
| 1787 | } | ||
| 1788 | return pa_central_init(&arena_pa_central_global, base, hpa, | ||
| 1789 | &hpa_hooks_default); | ||
| 1790 | } | ||
| 1791 | |||
| 1792 | void | ||
| 1793 | arena_prefork0(tsdn_t *tsdn, arena_t *arena) { | ||
| 1794 | pa_shard_prefork0(tsdn, &arena->pa_shard); | ||
| 1795 | } | ||
| 1796 | |||
| 1797 | void | ||
| 1798 | arena_prefork1(tsdn_t *tsdn, arena_t *arena) { | ||
| 1799 | if (config_stats) { | ||
| 1800 | malloc_mutex_prefork(tsdn, &arena->tcache_ql_mtx); | ||
| 1801 | } | ||
| 1802 | } | ||
| 1803 | |||
| 1804 | void | ||
| 1805 | arena_prefork2(tsdn_t *tsdn, arena_t *arena) { | ||
| 1806 | pa_shard_prefork2(tsdn, &arena->pa_shard); | ||
| 1807 | } | ||
| 1808 | |||
| 1809 | void | ||
| 1810 | arena_prefork3(tsdn_t *tsdn, arena_t *arena) { | ||
| 1811 | pa_shard_prefork3(tsdn, &arena->pa_shard); | ||
| 1812 | } | ||
| 1813 | |||
| 1814 | void | ||
| 1815 | arena_prefork4(tsdn_t *tsdn, arena_t *arena) { | ||
| 1816 | pa_shard_prefork4(tsdn, &arena->pa_shard); | ||
| 1817 | } | ||
| 1818 | |||
| 1819 | void | ||
| 1820 | arena_prefork5(tsdn_t *tsdn, arena_t *arena) { | ||
| 1821 | pa_shard_prefork5(tsdn, &arena->pa_shard); | ||
| 1822 | } | ||
| 1823 | |||
| 1824 | void | ||
| 1825 | arena_prefork6(tsdn_t *tsdn, arena_t *arena) { | ||
| 1826 | base_prefork(tsdn, arena->base); | ||
| 1827 | } | ||
| 1828 | |||
| 1829 | void | ||
| 1830 | arena_prefork7(tsdn_t *tsdn, arena_t *arena) { | ||
| 1831 | malloc_mutex_prefork(tsdn, &arena->large_mtx); | ||
| 1832 | } | ||
| 1833 | |||
| 1834 | void | ||
| 1835 | arena_prefork8(tsdn_t *tsdn, arena_t *arena) { | ||
| 1836 | for (unsigned i = 0; i < nbins_total; i++) { | ||
| 1837 | bin_prefork(tsdn, &arena->bins[i]); | ||
| 1838 | } | ||
| 1839 | } | ||
| 1840 | |||
| 1841 | void | ||
| 1842 | arena_postfork_parent(tsdn_t *tsdn, arena_t *arena) { | ||
| 1843 | for (unsigned i = 0; i < nbins_total; i++) { | ||
| 1844 | bin_postfork_parent(tsdn, &arena->bins[i]); | ||
| 1845 | } | ||
| 1846 | |||
| 1847 | malloc_mutex_postfork_parent(tsdn, &arena->large_mtx); | ||
| 1848 | base_postfork_parent(tsdn, arena->base); | ||
| 1849 | pa_shard_postfork_parent(tsdn, &arena->pa_shard); | ||
| 1850 | if (config_stats) { | ||
| 1851 | malloc_mutex_postfork_parent(tsdn, &arena->tcache_ql_mtx); | ||
| 1852 | } | ||
| 1853 | } | ||
| 1854 | |||
| 1855 | void | ||
| 1856 | arena_postfork_child(tsdn_t *tsdn, arena_t *arena) { | ||
| 1857 | atomic_store_u(&arena->nthreads[0], 0, ATOMIC_RELAXED); | ||
| 1858 | atomic_store_u(&arena->nthreads[1], 0, ATOMIC_RELAXED); | ||
| 1859 | if (tsd_arena_get(tsdn_tsd(tsdn)) == arena) { | ||
| 1860 | arena_nthreads_inc(arena, false); | ||
| 1861 | } | ||
| 1862 | if (tsd_iarena_get(tsdn_tsd(tsdn)) == arena) { | ||
| 1863 | arena_nthreads_inc(arena, true); | ||
| 1864 | } | ||
| 1865 | if (config_stats) { | ||
| 1866 | ql_new(&arena->tcache_ql); | ||
| 1867 | ql_new(&arena->cache_bin_array_descriptor_ql); | ||
| 1868 | tcache_slow_t *tcache_slow = tcache_slow_get(tsdn_tsd(tsdn)); | ||
| 1869 | if (tcache_slow != NULL && tcache_slow->arena == arena) { | ||
| 1870 | tcache_t *tcache = tcache_slow->tcache; | ||
| 1871 | ql_elm_new(tcache_slow, link); | ||
| 1872 | ql_tail_insert(&arena->tcache_ql, tcache_slow, link); | ||
| 1873 | cache_bin_array_descriptor_init( | ||
| 1874 | &tcache_slow->cache_bin_array_descriptor, | ||
| 1875 | tcache->bins); | ||
| 1876 | ql_tail_insert(&arena->cache_bin_array_descriptor_ql, | ||
| 1877 | &tcache_slow->cache_bin_array_descriptor, link); | ||
| 1878 | } | ||
| 1879 | } | ||
| 1880 | |||
| 1881 | for (unsigned i = 0; i < nbins_total; i++) { | ||
| 1882 | bin_postfork_child(tsdn, &arena->bins[i]); | ||
| 1883 | } | ||
| 1884 | |||
| 1885 | malloc_mutex_postfork_child(tsdn, &arena->large_mtx); | ||
| 1886 | base_postfork_child(tsdn, arena->base); | ||
| 1887 | pa_shard_postfork_child(tsdn, &arena->pa_shard); | ||
| 1888 | if (config_stats) { | ||
| 1889 | malloc_mutex_postfork_child(tsdn, &arena->tcache_ql_mtx); | ||
| 1890 | } | ||
| 1891 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/background_thread.c b/examples/redis-unstable/deps/jemalloc/src/background_thread.c deleted file mode 100644 index 3bb8d26..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/background_thread.c +++ /dev/null | |||
| @@ -1,820 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | |||
| 6 | JEMALLOC_DIAGNOSTIC_DISABLE_SPURIOUS | ||
| 7 | |||
| 8 | /******************************************************************************/ | ||
| 9 | /* Data. */ | ||
| 10 | |||
| 11 | /* This option should be opt-in only. */ | ||
| 12 | #define BACKGROUND_THREAD_DEFAULT false | ||
| 13 | /* Read-only after initialization. */ | ||
| 14 | bool opt_background_thread = BACKGROUND_THREAD_DEFAULT; | ||
| 15 | size_t opt_max_background_threads = MAX_BACKGROUND_THREAD_LIMIT + 1; | ||
| 16 | |||
| 17 | /* Used for thread creation, termination and stats. */ | ||
| 18 | malloc_mutex_t background_thread_lock; | ||
| 19 | /* Indicates global state. Atomic because decay reads this w/o locking. */ | ||
| 20 | atomic_b_t background_thread_enabled_state; | ||
| 21 | size_t n_background_threads; | ||
| 22 | size_t max_background_threads; | ||
| 23 | /* Thread info per-index. */ | ||
| 24 | background_thread_info_t *background_thread_info; | ||
| 25 | |||
| 26 | /******************************************************************************/ | ||
| 27 | |||
| 28 | #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER | ||
| 29 | |||
| 30 | static int (*pthread_create_fptr)(pthread_t *__restrict, const pthread_attr_t *, | ||
| 31 | void *(*)(void *), void *__restrict); | ||
| 32 | |||
| 33 | static void | ||
| 34 | pthread_create_wrapper_init(void) { | ||
| 35 | #ifdef JEMALLOC_LAZY_LOCK | ||
| 36 | if (!isthreaded) { | ||
| 37 | isthreaded = true; | ||
| 38 | } | ||
| 39 | #endif | ||
| 40 | } | ||
| 41 | |||
| 42 | int | ||
| 43 | pthread_create_wrapper(pthread_t *__restrict thread, const pthread_attr_t *attr, | ||
| 44 | void *(*start_routine)(void *), void *__restrict arg) { | ||
| 45 | pthread_create_wrapper_init(); | ||
| 46 | |||
| 47 | return pthread_create_fptr(thread, attr, start_routine, arg); | ||
| 48 | } | ||
| 49 | #endif /* JEMALLOC_PTHREAD_CREATE_WRAPPER */ | ||
| 50 | |||
| 51 | #ifndef JEMALLOC_BACKGROUND_THREAD | ||
| 52 | #define NOT_REACHED { not_reached(); } | ||
| 53 | bool background_thread_create(tsd_t *tsd, unsigned arena_ind) NOT_REACHED | ||
| 54 | bool background_threads_enable(tsd_t *tsd) NOT_REACHED | ||
| 55 | bool background_threads_disable(tsd_t *tsd) NOT_REACHED | ||
| 56 | bool background_thread_is_started(background_thread_info_t *info) NOT_REACHED | ||
| 57 | void background_thread_wakeup_early(background_thread_info_t *info, | ||
| 58 | nstime_t *remaining_sleep) NOT_REACHED | ||
| 59 | void background_thread_prefork0(tsdn_t *tsdn) NOT_REACHED | ||
| 60 | void background_thread_prefork1(tsdn_t *tsdn) NOT_REACHED | ||
| 61 | void background_thread_postfork_parent(tsdn_t *tsdn) NOT_REACHED | ||
| 62 | void background_thread_postfork_child(tsdn_t *tsdn) NOT_REACHED | ||
| 63 | bool background_thread_stats_read(tsdn_t *tsdn, | ||
| 64 | background_thread_stats_t *stats) NOT_REACHED | ||
| 65 | void background_thread_ctl_init(tsdn_t *tsdn) NOT_REACHED | ||
| 66 | #undef NOT_REACHED | ||
| 67 | #else | ||
| 68 | |||
| 69 | static bool background_thread_enabled_at_fork; | ||
| 70 | |||
| 71 | static void | ||
| 72 | background_thread_info_init(tsdn_t *tsdn, background_thread_info_t *info) { | ||
| 73 | background_thread_wakeup_time_set(tsdn, info, 0); | ||
| 74 | info->npages_to_purge_new = 0; | ||
| 75 | if (config_stats) { | ||
| 76 | info->tot_n_runs = 0; | ||
| 77 | nstime_init_zero(&info->tot_sleep_time); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | static inline bool | ||
| 82 | set_current_thread_affinity(int cpu) { | ||
| 83 | #if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) | ||
| 84 | cpu_set_t cpuset; | ||
| 85 | #else | ||
| 86 | # ifndef __NetBSD__ | ||
| 87 | cpuset_t cpuset; | ||
| 88 | # else | ||
| 89 | cpuset_t *cpuset; | ||
| 90 | # endif | ||
| 91 | #endif | ||
| 92 | |||
| 93 | #ifndef __NetBSD__ | ||
| 94 | CPU_ZERO(&cpuset); | ||
| 95 | CPU_SET(cpu, &cpuset); | ||
| 96 | #else | ||
| 97 | cpuset = cpuset_create(); | ||
| 98 | #endif | ||
| 99 | |||
| 100 | #if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) | ||
| 101 | return (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) != 0); | ||
| 102 | #else | ||
| 103 | # ifndef __NetBSD__ | ||
| 104 | int ret = pthread_setaffinity_np(pthread_self(), sizeof(cpuset_t), | ||
| 105 | &cpuset); | ||
| 106 | # else | ||
| 107 | int ret = pthread_setaffinity_np(pthread_self(), cpuset_size(cpuset), | ||
| 108 | cpuset); | ||
| 109 | cpuset_destroy(cpuset); | ||
| 110 | # endif | ||
| 111 | return ret != 0; | ||
| 112 | #endif | ||
| 113 | } | ||
| 114 | |||
| 115 | #define BILLION UINT64_C(1000000000) | ||
| 116 | /* Minimal sleep interval 100 ms. */ | ||
| 117 | #define BACKGROUND_THREAD_MIN_INTERVAL_NS (BILLION / 10) | ||
| 118 | |||
| 119 | static void | ||
| 120 | background_thread_sleep(tsdn_t *tsdn, background_thread_info_t *info, | ||
| 121 | uint64_t interval) { | ||
| 122 | if (config_stats) { | ||
| 123 | info->tot_n_runs++; | ||
| 124 | } | ||
| 125 | info->npages_to_purge_new = 0; | ||
| 126 | |||
| 127 | struct timeval tv; | ||
| 128 | /* Specific clock required by timedwait. */ | ||
| 129 | gettimeofday(&tv, NULL); | ||
| 130 | nstime_t before_sleep; | ||
| 131 | nstime_init2(&before_sleep, tv.tv_sec, tv.tv_usec * 1000); | ||
| 132 | |||
| 133 | int ret; | ||
| 134 | if (interval == BACKGROUND_THREAD_INDEFINITE_SLEEP) { | ||
| 135 | background_thread_wakeup_time_set(tsdn, info, | ||
| 136 | BACKGROUND_THREAD_INDEFINITE_SLEEP); | ||
| 137 | ret = pthread_cond_wait(&info->cond, &info->mtx.lock); | ||
| 138 | assert(ret == 0); | ||
| 139 | } else { | ||
| 140 | assert(interval >= BACKGROUND_THREAD_MIN_INTERVAL_NS && | ||
| 141 | interval <= BACKGROUND_THREAD_INDEFINITE_SLEEP); | ||
| 142 | /* We need malloc clock (can be different from tv). */ | ||
| 143 | nstime_t next_wakeup; | ||
| 144 | nstime_init_update(&next_wakeup); | ||
| 145 | nstime_iadd(&next_wakeup, interval); | ||
| 146 | assert(nstime_ns(&next_wakeup) < | ||
| 147 | BACKGROUND_THREAD_INDEFINITE_SLEEP); | ||
| 148 | background_thread_wakeup_time_set(tsdn, info, | ||
| 149 | nstime_ns(&next_wakeup)); | ||
| 150 | |||
| 151 | nstime_t ts_wakeup; | ||
| 152 | nstime_copy(&ts_wakeup, &before_sleep); | ||
| 153 | nstime_iadd(&ts_wakeup, interval); | ||
| 154 | struct timespec ts; | ||
| 155 | ts.tv_sec = (size_t)nstime_sec(&ts_wakeup); | ||
| 156 | ts.tv_nsec = (size_t)nstime_nsec(&ts_wakeup); | ||
| 157 | |||
| 158 | assert(!background_thread_indefinite_sleep(info)); | ||
| 159 | ret = pthread_cond_timedwait(&info->cond, &info->mtx.lock, &ts); | ||
| 160 | assert(ret == ETIMEDOUT || ret == 0); | ||
| 161 | } | ||
| 162 | if (config_stats) { | ||
| 163 | gettimeofday(&tv, NULL); | ||
| 164 | nstime_t after_sleep; | ||
| 165 | nstime_init2(&after_sleep, tv.tv_sec, tv.tv_usec * 1000); | ||
| 166 | if (nstime_compare(&after_sleep, &before_sleep) > 0) { | ||
| 167 | nstime_subtract(&after_sleep, &before_sleep); | ||
| 168 | nstime_add(&info->tot_sleep_time, &after_sleep); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | static bool | ||
| 174 | background_thread_pause_check(tsdn_t *tsdn, background_thread_info_t *info) { | ||
| 175 | if (unlikely(info->state == background_thread_paused)) { | ||
| 176 | malloc_mutex_unlock(tsdn, &info->mtx); | ||
| 177 | /* Wait on global lock to update status. */ | ||
| 178 | malloc_mutex_lock(tsdn, &background_thread_lock); | ||
| 179 | malloc_mutex_unlock(tsdn, &background_thread_lock); | ||
| 180 | malloc_mutex_lock(tsdn, &info->mtx); | ||
| 181 | return true; | ||
| 182 | } | ||
| 183 | |||
| 184 | return false; | ||
| 185 | } | ||
| 186 | |||
| 187 | static inline void | ||
| 188 | background_work_sleep_once(tsdn_t *tsdn, background_thread_info_t *info, | ||
| 189 | unsigned ind) { | ||
| 190 | uint64_t ns_until_deferred = BACKGROUND_THREAD_DEFERRED_MAX; | ||
| 191 | unsigned narenas = narenas_total_get(); | ||
| 192 | bool slept_indefinitely = background_thread_indefinite_sleep(info); | ||
| 193 | |||
| 194 | for (unsigned i = ind; i < narenas; i += max_background_threads) { | ||
| 195 | arena_t *arena = arena_get(tsdn, i, false); | ||
| 196 | if (!arena) { | ||
| 197 | continue; | ||
| 198 | } | ||
| 199 | /* | ||
| 200 | * If thread was woken up from the indefinite sleep, don't | ||
| 201 | * do the work instantly, but rather check when the deferred | ||
| 202 | * work that caused this thread to wake up is scheduled for. | ||
| 203 | */ | ||
| 204 | if (!slept_indefinitely) { | ||
| 205 | arena_do_deferred_work(tsdn, arena); | ||
| 206 | } | ||
| 207 | if (ns_until_deferred <= BACKGROUND_THREAD_MIN_INTERVAL_NS) { | ||
| 208 | /* Min interval will be used. */ | ||
| 209 | continue; | ||
| 210 | } | ||
| 211 | uint64_t ns_arena_deferred = pa_shard_time_until_deferred_work( | ||
| 212 | tsdn, &arena->pa_shard); | ||
| 213 | if (ns_arena_deferred < ns_until_deferred) { | ||
| 214 | ns_until_deferred = ns_arena_deferred; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | uint64_t sleep_ns; | ||
| 219 | if (ns_until_deferred == BACKGROUND_THREAD_DEFERRED_MAX) { | ||
| 220 | sleep_ns = BACKGROUND_THREAD_INDEFINITE_SLEEP; | ||
| 221 | } else { | ||
| 222 | sleep_ns = | ||
| 223 | (ns_until_deferred < BACKGROUND_THREAD_MIN_INTERVAL_NS) | ||
| 224 | ? BACKGROUND_THREAD_MIN_INTERVAL_NS | ||
| 225 | : ns_until_deferred; | ||
| 226 | |||
| 227 | } | ||
| 228 | |||
| 229 | background_thread_sleep(tsdn, info, sleep_ns); | ||
| 230 | } | ||
| 231 | |||
| 232 | static bool | ||
| 233 | background_threads_disable_single(tsd_t *tsd, background_thread_info_t *info) { | ||
| 234 | if (info == &background_thread_info[0]) { | ||
| 235 | malloc_mutex_assert_owner(tsd_tsdn(tsd), | ||
| 236 | &background_thread_lock); | ||
| 237 | } else { | ||
| 238 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), | ||
| 239 | &background_thread_lock); | ||
| 240 | } | ||
| 241 | |||
| 242 | pre_reentrancy(tsd, NULL); | ||
| 243 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 244 | bool has_thread; | ||
| 245 | assert(info->state != background_thread_paused); | ||
| 246 | if (info->state == background_thread_started) { | ||
| 247 | has_thread = true; | ||
| 248 | info->state = background_thread_stopped; | ||
| 249 | pthread_cond_signal(&info->cond); | ||
| 250 | } else { | ||
| 251 | has_thread = false; | ||
| 252 | } | ||
| 253 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 254 | |||
| 255 | if (!has_thread) { | ||
| 256 | post_reentrancy(tsd); | ||
| 257 | return false; | ||
| 258 | } | ||
| 259 | void *ret; | ||
| 260 | if (pthread_join(info->thread, &ret)) { | ||
| 261 | post_reentrancy(tsd); | ||
| 262 | return true; | ||
| 263 | } | ||
| 264 | assert(ret == NULL); | ||
| 265 | n_background_threads--; | ||
| 266 | post_reentrancy(tsd); | ||
| 267 | |||
| 268 | return false; | ||
| 269 | } | ||
| 270 | |||
| 271 | static void *background_thread_entry(void *ind_arg); | ||
| 272 | |||
| 273 | static int | ||
| 274 | background_thread_create_signals_masked(pthread_t *thread, | ||
| 275 | const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { | ||
| 276 | /* | ||
| 277 | * Mask signals during thread creation so that the thread inherits | ||
| 278 | * an empty signal set. | ||
| 279 | */ | ||
| 280 | sigset_t set; | ||
| 281 | sigfillset(&set); | ||
| 282 | sigset_t oldset; | ||
| 283 | int mask_err = pthread_sigmask(SIG_SETMASK, &set, &oldset); | ||
| 284 | if (mask_err != 0) { | ||
| 285 | return mask_err; | ||
| 286 | } | ||
| 287 | int create_err = pthread_create_wrapper(thread, attr, start_routine, | ||
| 288 | arg); | ||
| 289 | /* | ||
| 290 | * Restore the signal mask. Failure to restore the signal mask here | ||
| 291 | * changes program behavior. | ||
| 292 | */ | ||
| 293 | int restore_err = pthread_sigmask(SIG_SETMASK, &oldset, NULL); | ||
| 294 | if (restore_err != 0) { | ||
| 295 | malloc_printf("<jemalloc>: background thread creation " | ||
| 296 | "failed (%d), and signal mask restoration failed " | ||
| 297 | "(%d)\n", create_err, restore_err); | ||
| 298 | if (opt_abort) { | ||
| 299 | abort(); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | return create_err; | ||
| 303 | } | ||
| 304 | |||
| 305 | static bool | ||
| 306 | check_background_thread_creation(tsd_t *tsd, unsigned *n_created, | ||
| 307 | bool *created_threads) { | ||
| 308 | bool ret = false; | ||
| 309 | if (likely(*n_created == n_background_threads)) { | ||
| 310 | return ret; | ||
| 311 | } | ||
| 312 | |||
| 313 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 314 | malloc_mutex_unlock(tsdn, &background_thread_info[0].mtx); | ||
| 315 | for (unsigned i = 1; i < max_background_threads; i++) { | ||
| 316 | if (created_threads[i]) { | ||
| 317 | continue; | ||
| 318 | } | ||
| 319 | background_thread_info_t *info = &background_thread_info[i]; | ||
| 320 | malloc_mutex_lock(tsdn, &info->mtx); | ||
| 321 | /* | ||
| 322 | * In case of the background_thread_paused state because of | ||
| 323 | * arena reset, delay the creation. | ||
| 324 | */ | ||
| 325 | bool create = (info->state == background_thread_started); | ||
| 326 | malloc_mutex_unlock(tsdn, &info->mtx); | ||
| 327 | if (!create) { | ||
| 328 | continue; | ||
| 329 | } | ||
| 330 | |||
| 331 | pre_reentrancy(tsd, NULL); | ||
| 332 | int err = background_thread_create_signals_masked(&info->thread, | ||
| 333 | NULL, background_thread_entry, (void *)(uintptr_t)i); | ||
| 334 | post_reentrancy(tsd); | ||
| 335 | |||
| 336 | if (err == 0) { | ||
| 337 | (*n_created)++; | ||
| 338 | created_threads[i] = true; | ||
| 339 | } else { | ||
| 340 | malloc_printf("<jemalloc>: background thread " | ||
| 341 | "creation failed (%d)\n", err); | ||
| 342 | if (opt_abort) { | ||
| 343 | abort(); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | /* Return to restart the loop since we unlocked. */ | ||
| 347 | ret = true; | ||
| 348 | break; | ||
| 349 | } | ||
| 350 | malloc_mutex_lock(tsdn, &background_thread_info[0].mtx); | ||
| 351 | |||
| 352 | return ret; | ||
| 353 | } | ||
| 354 | |||
| 355 | static void | ||
| 356 | background_thread0_work(tsd_t *tsd) { | ||
| 357 | /* Thread0 is also responsible for launching / terminating threads. */ | ||
| 358 | VARIABLE_ARRAY(bool, created_threads, max_background_threads); | ||
| 359 | unsigned i; | ||
| 360 | for (i = 1; i < max_background_threads; i++) { | ||
| 361 | created_threads[i] = false; | ||
| 362 | } | ||
| 363 | /* Start working, and create more threads when asked. */ | ||
| 364 | unsigned n_created = 1; | ||
| 365 | while (background_thread_info[0].state != background_thread_stopped) { | ||
| 366 | if (background_thread_pause_check(tsd_tsdn(tsd), | ||
| 367 | &background_thread_info[0])) { | ||
| 368 | continue; | ||
| 369 | } | ||
| 370 | if (check_background_thread_creation(tsd, &n_created, | ||
| 371 | (bool *)&created_threads)) { | ||
| 372 | continue; | ||
| 373 | } | ||
| 374 | background_work_sleep_once(tsd_tsdn(tsd), | ||
| 375 | &background_thread_info[0], 0); | ||
| 376 | } | ||
| 377 | |||
| 378 | /* | ||
| 379 | * Shut down other threads at exit. Note that the ctl thread is holding | ||
| 380 | * the global background_thread mutex (and is waiting) for us. | ||
| 381 | */ | ||
| 382 | assert(!background_thread_enabled()); | ||
| 383 | for (i = 1; i < max_background_threads; i++) { | ||
| 384 | background_thread_info_t *info = &background_thread_info[i]; | ||
| 385 | assert(info->state != background_thread_paused); | ||
| 386 | if (created_threads[i]) { | ||
| 387 | background_threads_disable_single(tsd, info); | ||
| 388 | } else { | ||
| 389 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 390 | if (info->state != background_thread_stopped) { | ||
| 391 | /* The thread was not created. */ | ||
| 392 | assert(info->state == | ||
| 393 | background_thread_started); | ||
| 394 | n_background_threads--; | ||
| 395 | info->state = background_thread_stopped; | ||
| 396 | } | ||
| 397 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 398 | } | ||
| 399 | } | ||
| 400 | background_thread_info[0].state = background_thread_stopped; | ||
| 401 | assert(n_background_threads == 1); | ||
| 402 | } | ||
| 403 | |||
| 404 | static void | ||
| 405 | background_work(tsd_t *tsd, unsigned ind) { | ||
| 406 | background_thread_info_t *info = &background_thread_info[ind]; | ||
| 407 | |||
| 408 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 409 | background_thread_wakeup_time_set(tsd_tsdn(tsd), info, | ||
| 410 | BACKGROUND_THREAD_INDEFINITE_SLEEP); | ||
| 411 | if (ind == 0) { | ||
| 412 | background_thread0_work(tsd); | ||
| 413 | } else { | ||
| 414 | while (info->state != background_thread_stopped) { | ||
| 415 | if (background_thread_pause_check(tsd_tsdn(tsd), | ||
| 416 | info)) { | ||
| 417 | continue; | ||
| 418 | } | ||
| 419 | background_work_sleep_once(tsd_tsdn(tsd), info, ind); | ||
| 420 | } | ||
| 421 | } | ||
| 422 | assert(info->state == background_thread_stopped); | ||
| 423 | background_thread_wakeup_time_set(tsd_tsdn(tsd), info, 0); | ||
| 424 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 425 | } | ||
| 426 | |||
| 427 | static void * | ||
| 428 | background_thread_entry(void *ind_arg) { | ||
| 429 | unsigned thread_ind = (unsigned)(uintptr_t)ind_arg; | ||
| 430 | assert(thread_ind < max_background_threads); | ||
| 431 | #ifdef JEMALLOC_HAVE_PTHREAD_SETNAME_NP | ||
| 432 | pthread_setname_np(pthread_self(), "jemalloc_bg_thd"); | ||
| 433 | #elif defined(__FreeBSD__) || defined(__DragonFly__) | ||
| 434 | pthread_set_name_np(pthread_self(), "jemalloc_bg_thd"); | ||
| 435 | #endif | ||
| 436 | if (opt_percpu_arena != percpu_arena_disabled) { | ||
| 437 | set_current_thread_affinity((int)thread_ind); | ||
| 438 | } | ||
| 439 | /* | ||
| 440 | * Start periodic background work. We use internal tsd which avoids | ||
| 441 | * side effects, for example triggering new arena creation (which in | ||
| 442 | * turn triggers another background thread creation). | ||
| 443 | */ | ||
| 444 | background_work(tsd_internal_fetch(), thread_ind); | ||
| 445 | assert(pthread_equal(pthread_self(), | ||
| 446 | background_thread_info[thread_ind].thread)); | ||
| 447 | |||
| 448 | return NULL; | ||
| 449 | } | ||
| 450 | |||
| 451 | static void | ||
| 452 | background_thread_init(tsd_t *tsd, background_thread_info_t *info) { | ||
| 453 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); | ||
| 454 | info->state = background_thread_started; | ||
| 455 | background_thread_info_init(tsd_tsdn(tsd), info); | ||
| 456 | n_background_threads++; | ||
| 457 | } | ||
| 458 | |||
| 459 | static bool | ||
| 460 | background_thread_create_locked(tsd_t *tsd, unsigned arena_ind) { | ||
| 461 | assert(have_background_thread); | ||
| 462 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); | ||
| 463 | |||
| 464 | /* We create at most NCPUs threads. */ | ||
| 465 | size_t thread_ind = arena_ind % max_background_threads; | ||
| 466 | background_thread_info_t *info = &background_thread_info[thread_ind]; | ||
| 467 | |||
| 468 | bool need_new_thread; | ||
| 469 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 470 | need_new_thread = background_thread_enabled() && | ||
| 471 | (info->state == background_thread_stopped); | ||
| 472 | if (need_new_thread) { | ||
| 473 | background_thread_init(tsd, info); | ||
| 474 | } | ||
| 475 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 476 | if (!need_new_thread) { | ||
| 477 | return false; | ||
| 478 | } | ||
| 479 | if (arena_ind != 0) { | ||
| 480 | /* Threads are created asynchronously by Thread 0. */ | ||
| 481 | background_thread_info_t *t0 = &background_thread_info[0]; | ||
| 482 | malloc_mutex_lock(tsd_tsdn(tsd), &t0->mtx); | ||
| 483 | assert(t0->state == background_thread_started); | ||
| 484 | pthread_cond_signal(&t0->cond); | ||
| 485 | malloc_mutex_unlock(tsd_tsdn(tsd), &t0->mtx); | ||
| 486 | |||
| 487 | return false; | ||
| 488 | } | ||
| 489 | |||
| 490 | pre_reentrancy(tsd, NULL); | ||
| 491 | /* | ||
| 492 | * To avoid complications (besides reentrancy), create internal | ||
| 493 | * background threads with the underlying pthread_create. | ||
| 494 | */ | ||
| 495 | int err = background_thread_create_signals_masked(&info->thread, NULL, | ||
| 496 | background_thread_entry, (void *)thread_ind); | ||
| 497 | post_reentrancy(tsd); | ||
| 498 | |||
| 499 | if (err != 0) { | ||
| 500 | malloc_printf("<jemalloc>: arena 0 background thread creation " | ||
| 501 | "failed (%d)\n", err); | ||
| 502 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 503 | info->state = background_thread_stopped; | ||
| 504 | n_background_threads--; | ||
| 505 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 506 | |||
| 507 | return true; | ||
| 508 | } | ||
| 509 | |||
| 510 | return false; | ||
| 511 | } | ||
| 512 | |||
| 513 | /* Create a new background thread if needed. */ | ||
| 514 | bool | ||
| 515 | background_thread_create(tsd_t *tsd, unsigned arena_ind) { | ||
| 516 | assert(have_background_thread); | ||
| 517 | |||
| 518 | bool ret; | ||
| 519 | malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 520 | ret = background_thread_create_locked(tsd, arena_ind); | ||
| 521 | malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 522 | |||
| 523 | return ret; | ||
| 524 | } | ||
| 525 | |||
| 526 | bool | ||
| 527 | background_threads_enable(tsd_t *tsd) { | ||
| 528 | assert(n_background_threads == 0); | ||
| 529 | assert(background_thread_enabled()); | ||
| 530 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); | ||
| 531 | |||
| 532 | VARIABLE_ARRAY(bool, marked, max_background_threads); | ||
| 533 | unsigned nmarked; | ||
| 534 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 535 | marked[i] = false; | ||
| 536 | } | ||
| 537 | nmarked = 0; | ||
| 538 | /* Thread 0 is required and created at the end. */ | ||
| 539 | marked[0] = true; | ||
| 540 | /* Mark the threads we need to create for thread 0. */ | ||
| 541 | unsigned narenas = narenas_total_get(); | ||
| 542 | for (unsigned i = 1; i < narenas; i++) { | ||
| 543 | if (marked[i % max_background_threads] || | ||
| 544 | arena_get(tsd_tsdn(tsd), i, false) == NULL) { | ||
| 545 | continue; | ||
| 546 | } | ||
| 547 | background_thread_info_t *info = &background_thread_info[ | ||
| 548 | i % max_background_threads]; | ||
| 549 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 550 | assert(info->state == background_thread_stopped); | ||
| 551 | background_thread_init(tsd, info); | ||
| 552 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 553 | marked[i % max_background_threads] = true; | ||
| 554 | if (++nmarked == max_background_threads) { | ||
| 555 | break; | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | bool err = background_thread_create_locked(tsd, 0); | ||
| 560 | if (err) { | ||
| 561 | return true; | ||
| 562 | } | ||
| 563 | for (unsigned i = 0; i < narenas; i++) { | ||
| 564 | arena_t *arena = arena_get(tsd_tsdn(tsd), i, false); | ||
| 565 | if (arena != NULL) { | ||
| 566 | pa_shard_set_deferral_allowed(tsd_tsdn(tsd), | ||
| 567 | &arena->pa_shard, true); | ||
| 568 | } | ||
| 569 | } | ||
| 570 | return false; | ||
| 571 | } | ||
| 572 | |||
| 573 | bool | ||
| 574 | background_threads_disable(tsd_t *tsd) { | ||
| 575 | assert(!background_thread_enabled()); | ||
| 576 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock); | ||
| 577 | |||
| 578 | /* Thread 0 will be responsible for terminating other threads. */ | ||
| 579 | if (background_threads_disable_single(tsd, | ||
| 580 | &background_thread_info[0])) { | ||
| 581 | return true; | ||
| 582 | } | ||
| 583 | assert(n_background_threads == 0); | ||
| 584 | unsigned narenas = narenas_total_get(); | ||
| 585 | for (unsigned i = 0; i < narenas; i++) { | ||
| 586 | arena_t *arena = arena_get(tsd_tsdn(tsd), i, false); | ||
| 587 | if (arena != NULL) { | ||
| 588 | pa_shard_set_deferral_allowed(tsd_tsdn(tsd), | ||
| 589 | &arena->pa_shard, false); | ||
| 590 | } | ||
| 591 | } | ||
| 592 | |||
| 593 | return false; | ||
| 594 | } | ||
| 595 | |||
| 596 | bool | ||
| 597 | background_thread_is_started(background_thread_info_t *info) { | ||
| 598 | return info->state == background_thread_started; | ||
| 599 | } | ||
| 600 | |||
| 601 | void | ||
| 602 | background_thread_wakeup_early(background_thread_info_t *info, | ||
| 603 | nstime_t *remaining_sleep) { | ||
| 604 | /* | ||
| 605 | * This is an optimization to increase batching. At this point | ||
| 606 | * we know that background thread wakes up soon, so the time to cache | ||
| 607 | * the just freed memory is bounded and low. | ||
| 608 | */ | ||
| 609 | if (remaining_sleep != NULL && nstime_ns(remaining_sleep) < | ||
| 610 | BACKGROUND_THREAD_MIN_INTERVAL_NS) { | ||
| 611 | return; | ||
| 612 | } | ||
| 613 | pthread_cond_signal(&info->cond); | ||
| 614 | } | ||
| 615 | |||
| 616 | void | ||
| 617 | background_thread_prefork0(tsdn_t *tsdn) { | ||
| 618 | malloc_mutex_prefork(tsdn, &background_thread_lock); | ||
| 619 | background_thread_enabled_at_fork = background_thread_enabled(); | ||
| 620 | } | ||
| 621 | |||
| 622 | void | ||
| 623 | background_thread_prefork1(tsdn_t *tsdn) { | ||
| 624 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 625 | malloc_mutex_prefork(tsdn, &background_thread_info[i].mtx); | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | void | ||
| 630 | background_thread_postfork_parent(tsdn_t *tsdn) { | ||
| 631 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 632 | malloc_mutex_postfork_parent(tsdn, | ||
| 633 | &background_thread_info[i].mtx); | ||
| 634 | } | ||
| 635 | malloc_mutex_postfork_parent(tsdn, &background_thread_lock); | ||
| 636 | } | ||
| 637 | |||
| 638 | void | ||
| 639 | background_thread_postfork_child(tsdn_t *tsdn) { | ||
| 640 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 641 | malloc_mutex_postfork_child(tsdn, | ||
| 642 | &background_thread_info[i].mtx); | ||
| 643 | } | ||
| 644 | malloc_mutex_postfork_child(tsdn, &background_thread_lock); | ||
| 645 | if (!background_thread_enabled_at_fork) { | ||
| 646 | return; | ||
| 647 | } | ||
| 648 | |||
| 649 | /* Clear background_thread state (reset to disabled for child). */ | ||
| 650 | malloc_mutex_lock(tsdn, &background_thread_lock); | ||
| 651 | n_background_threads = 0; | ||
| 652 | background_thread_enabled_set(tsdn, false); | ||
| 653 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 654 | background_thread_info_t *info = &background_thread_info[i]; | ||
| 655 | malloc_mutex_lock(tsdn, &info->mtx); | ||
| 656 | info->state = background_thread_stopped; | ||
| 657 | int ret = pthread_cond_init(&info->cond, NULL); | ||
| 658 | assert(ret == 0); | ||
| 659 | background_thread_info_init(tsdn, info); | ||
| 660 | malloc_mutex_unlock(tsdn, &info->mtx); | ||
| 661 | } | ||
| 662 | malloc_mutex_unlock(tsdn, &background_thread_lock); | ||
| 663 | } | ||
| 664 | |||
| 665 | bool | ||
| 666 | background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) { | ||
| 667 | assert(config_stats); | ||
| 668 | malloc_mutex_lock(tsdn, &background_thread_lock); | ||
| 669 | if (!background_thread_enabled()) { | ||
| 670 | malloc_mutex_unlock(tsdn, &background_thread_lock); | ||
| 671 | return true; | ||
| 672 | } | ||
| 673 | |||
| 674 | nstime_init_zero(&stats->run_interval); | ||
| 675 | memset(&stats->max_counter_per_bg_thd, 0, sizeof(mutex_prof_data_t)); | ||
| 676 | |||
| 677 | uint64_t num_runs = 0; | ||
| 678 | stats->num_threads = n_background_threads; | ||
| 679 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 680 | background_thread_info_t *info = &background_thread_info[i]; | ||
| 681 | if (malloc_mutex_trylock(tsdn, &info->mtx)) { | ||
| 682 | /* | ||
| 683 | * Each background thread run may take a long time; | ||
| 684 | * avoid waiting on the stats if the thread is active. | ||
| 685 | */ | ||
| 686 | continue; | ||
| 687 | } | ||
| 688 | if (info->state != background_thread_stopped) { | ||
| 689 | num_runs += info->tot_n_runs; | ||
| 690 | nstime_add(&stats->run_interval, &info->tot_sleep_time); | ||
| 691 | malloc_mutex_prof_max_update(tsdn, | ||
| 692 | &stats->max_counter_per_bg_thd, &info->mtx); | ||
| 693 | } | ||
| 694 | malloc_mutex_unlock(tsdn, &info->mtx); | ||
| 695 | } | ||
| 696 | stats->num_runs = num_runs; | ||
| 697 | if (num_runs > 0) { | ||
| 698 | nstime_idivide(&stats->run_interval, num_runs); | ||
| 699 | } | ||
| 700 | malloc_mutex_unlock(tsdn, &background_thread_lock); | ||
| 701 | |||
| 702 | return false; | ||
| 703 | } | ||
| 704 | |||
| 705 | #undef BACKGROUND_THREAD_NPAGES_THRESHOLD | ||
| 706 | #undef BILLION | ||
| 707 | #undef BACKGROUND_THREAD_MIN_INTERVAL_NS | ||
| 708 | |||
| 709 | #ifdef JEMALLOC_HAVE_DLSYM | ||
| 710 | #include <dlfcn.h> | ||
| 711 | #endif | ||
| 712 | |||
| 713 | static bool | ||
| 714 | pthread_create_fptr_init(void) { | ||
| 715 | if (pthread_create_fptr != NULL) { | ||
| 716 | return false; | ||
| 717 | } | ||
| 718 | /* | ||
| 719 | * Try the next symbol first, because 1) when use lazy_lock we have a | ||
| 720 | * wrapper for pthread_create; and 2) application may define its own | ||
| 721 | * wrapper as well (and can call malloc within the wrapper). | ||
| 722 | */ | ||
| 723 | #ifdef JEMALLOC_HAVE_DLSYM | ||
| 724 | pthread_create_fptr = dlsym(RTLD_NEXT, "pthread_create"); | ||
| 725 | #else | ||
| 726 | pthread_create_fptr = NULL; | ||
| 727 | #endif | ||
| 728 | if (pthread_create_fptr == NULL) { | ||
| 729 | if (config_lazy_lock) { | ||
| 730 | malloc_write("<jemalloc>: Error in dlsym(RTLD_NEXT, " | ||
| 731 | "\"pthread_create\")\n"); | ||
| 732 | abort(); | ||
| 733 | } else { | ||
| 734 | /* Fall back to the default symbol. */ | ||
| 735 | pthread_create_fptr = pthread_create; | ||
| 736 | } | ||
| 737 | } | ||
| 738 | |||
| 739 | return false; | ||
| 740 | } | ||
| 741 | |||
| 742 | /* | ||
| 743 | * When lazy lock is enabled, we need to make sure setting isthreaded before | ||
| 744 | * taking any background_thread locks. This is called early in ctl (instead of | ||
| 745 | * wait for the pthread_create calls to trigger) because the mutex is required | ||
| 746 | * before creating background threads. | ||
| 747 | */ | ||
| 748 | void | ||
| 749 | background_thread_ctl_init(tsdn_t *tsdn) { | ||
| 750 | malloc_mutex_assert_not_owner(tsdn, &background_thread_lock); | ||
| 751 | #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER | ||
| 752 | pthread_create_fptr_init(); | ||
| 753 | pthread_create_wrapper_init(); | ||
| 754 | #endif | ||
| 755 | } | ||
| 756 | |||
| 757 | #endif /* defined(JEMALLOC_BACKGROUND_THREAD) */ | ||
| 758 | |||
| 759 | bool | ||
| 760 | background_thread_boot0(void) { | ||
| 761 | if (!have_background_thread && opt_background_thread) { | ||
| 762 | malloc_printf("<jemalloc>: option background_thread currently " | ||
| 763 | "supports pthread only\n"); | ||
| 764 | return true; | ||
| 765 | } | ||
| 766 | #ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER | ||
| 767 | if ((config_lazy_lock || opt_background_thread) && | ||
| 768 | pthread_create_fptr_init()) { | ||
| 769 | return true; | ||
| 770 | } | ||
| 771 | #endif | ||
| 772 | return false; | ||
| 773 | } | ||
| 774 | |||
| 775 | bool | ||
| 776 | background_thread_boot1(tsdn_t *tsdn, base_t *base) { | ||
| 777 | #ifdef JEMALLOC_BACKGROUND_THREAD | ||
| 778 | assert(have_background_thread); | ||
| 779 | assert(narenas_total_get() > 0); | ||
| 780 | |||
| 781 | if (opt_max_background_threads > MAX_BACKGROUND_THREAD_LIMIT) { | ||
| 782 | opt_max_background_threads = DEFAULT_NUM_BACKGROUND_THREAD; | ||
| 783 | } | ||
| 784 | max_background_threads = opt_max_background_threads; | ||
| 785 | |||
| 786 | background_thread_enabled_set(tsdn, opt_background_thread); | ||
| 787 | if (malloc_mutex_init(&background_thread_lock, | ||
| 788 | "background_thread_global", | ||
| 789 | WITNESS_RANK_BACKGROUND_THREAD_GLOBAL, | ||
| 790 | malloc_mutex_rank_exclusive)) { | ||
| 791 | return true; | ||
| 792 | } | ||
| 793 | |||
| 794 | background_thread_info = (background_thread_info_t *)base_alloc(tsdn, | ||
| 795 | base, opt_max_background_threads * | ||
| 796 | sizeof(background_thread_info_t), CACHELINE); | ||
| 797 | if (background_thread_info == NULL) { | ||
| 798 | return true; | ||
| 799 | } | ||
| 800 | |||
| 801 | for (unsigned i = 0; i < max_background_threads; i++) { | ||
| 802 | background_thread_info_t *info = &background_thread_info[i]; | ||
| 803 | /* Thread mutex is rank_inclusive because of thread0. */ | ||
| 804 | if (malloc_mutex_init(&info->mtx, "background_thread", | ||
| 805 | WITNESS_RANK_BACKGROUND_THREAD, | ||
| 806 | malloc_mutex_address_ordered)) { | ||
| 807 | return true; | ||
| 808 | } | ||
| 809 | if (pthread_cond_init(&info->cond, NULL)) { | ||
| 810 | return true; | ||
| 811 | } | ||
| 812 | malloc_mutex_lock(tsdn, &info->mtx); | ||
| 813 | info->state = background_thread_stopped; | ||
| 814 | background_thread_info_init(tsdn, info); | ||
| 815 | malloc_mutex_unlock(tsdn, &info->mtx); | ||
| 816 | } | ||
| 817 | #endif | ||
| 818 | |||
| 819 | return false; | ||
| 820 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/base.c b/examples/redis-unstable/deps/jemalloc/src/base.c deleted file mode 100644 index 7f4d675..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/base.c +++ /dev/null | |||
| @@ -1,529 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/extent_mmap.h" | ||
| 6 | #include "jemalloc/internal/mutex.h" | ||
| 7 | #include "jemalloc/internal/sz.h" | ||
| 8 | |||
| 9 | /* | ||
| 10 | * In auto mode, arenas switch to huge pages for the base allocator on the | ||
| 11 | * second base block. a0 switches to thp on the 5th block (after 20 megabytes | ||
| 12 | * of metadata), since more metadata (e.g. rtree nodes) come from a0's base. | ||
| 13 | */ | ||
| 14 | |||
| 15 | #define BASE_AUTO_THP_THRESHOLD 2 | ||
| 16 | #define BASE_AUTO_THP_THRESHOLD_A0 5 | ||
| 17 | |||
| 18 | /******************************************************************************/ | ||
| 19 | /* Data. */ | ||
| 20 | |||
| 21 | static base_t *b0; | ||
| 22 | |||
| 23 | metadata_thp_mode_t opt_metadata_thp = METADATA_THP_DEFAULT; | ||
| 24 | |||
| 25 | const char *metadata_thp_mode_names[] = { | ||
| 26 | "disabled", | ||
| 27 | "auto", | ||
| 28 | "always" | ||
| 29 | }; | ||
| 30 | |||
| 31 | /******************************************************************************/ | ||
| 32 | |||
| 33 | static inline bool | ||
| 34 | metadata_thp_madvise(void) { | ||
| 35 | return (metadata_thp_enabled() && | ||
| 36 | (init_system_thp_mode == thp_mode_default)); | ||
| 37 | } | ||
| 38 | |||
| 39 | static void * | ||
| 40 | base_map(tsdn_t *tsdn, ehooks_t *ehooks, unsigned ind, size_t size) { | ||
| 41 | void *addr; | ||
| 42 | bool zero = true; | ||
| 43 | bool commit = true; | ||
| 44 | |||
| 45 | /* Use huge page sizes and alignment regardless of opt_metadata_thp. */ | ||
| 46 | assert(size == HUGEPAGE_CEILING(size)); | ||
| 47 | size_t alignment = HUGEPAGE; | ||
| 48 | if (ehooks_are_default(ehooks)) { | ||
| 49 | addr = extent_alloc_mmap(NULL, size, alignment, &zero, &commit); | ||
| 50 | if (have_madvise_huge && addr) { | ||
| 51 | pages_set_thp_state(addr, size); | ||
| 52 | } | ||
| 53 | } else { | ||
| 54 | addr = ehooks_alloc(tsdn, ehooks, NULL, size, alignment, &zero, | ||
| 55 | &commit); | ||
| 56 | } | ||
| 57 | |||
| 58 | return addr; | ||
| 59 | } | ||
| 60 | |||
| 61 | static void | ||
| 62 | base_unmap(tsdn_t *tsdn, ehooks_t *ehooks, unsigned ind, void *addr, | ||
| 63 | size_t size) { | ||
| 64 | /* | ||
| 65 | * Cascade through dalloc, decommit, purge_forced, and purge_lazy, | ||
| 66 | * stopping at first success. This cascade is performed for consistency | ||
| 67 | * with the cascade in extent_dalloc_wrapper() because an application's | ||
| 68 | * custom hooks may not support e.g. dalloc. This function is only ever | ||
| 69 | * called as a side effect of arena destruction, so although it might | ||
| 70 | * seem pointless to do anything besides dalloc here, the application | ||
| 71 | * may in fact want the end state of all associated virtual memory to be | ||
| 72 | * in some consistent-but-allocated state. | ||
| 73 | */ | ||
| 74 | if (ehooks_are_default(ehooks)) { | ||
| 75 | if (!extent_dalloc_mmap(addr, size)) { | ||
| 76 | goto label_done; | ||
| 77 | } | ||
| 78 | if (!pages_decommit(addr, size)) { | ||
| 79 | goto label_done; | ||
| 80 | } | ||
| 81 | if (!pages_purge_forced(addr, size)) { | ||
| 82 | goto label_done; | ||
| 83 | } | ||
| 84 | if (!pages_purge_lazy(addr, size)) { | ||
| 85 | goto label_done; | ||
| 86 | } | ||
| 87 | /* Nothing worked. This should never happen. */ | ||
| 88 | not_reached(); | ||
| 89 | } else { | ||
| 90 | if (!ehooks_dalloc(tsdn, ehooks, addr, size, true)) { | ||
| 91 | goto label_done; | ||
| 92 | } | ||
| 93 | if (!ehooks_decommit(tsdn, ehooks, addr, size, 0, size)) { | ||
| 94 | goto label_done; | ||
| 95 | } | ||
| 96 | if (!ehooks_purge_forced(tsdn, ehooks, addr, size, 0, size)) { | ||
| 97 | goto label_done; | ||
| 98 | } | ||
| 99 | if (!ehooks_purge_lazy(tsdn, ehooks, addr, size, 0, size)) { | ||
| 100 | goto label_done; | ||
| 101 | } | ||
| 102 | /* Nothing worked. That's the application's problem. */ | ||
| 103 | } | ||
| 104 | label_done: | ||
| 105 | if (metadata_thp_madvise()) { | ||
| 106 | /* Set NOHUGEPAGE after unmap to avoid kernel defrag. */ | ||
| 107 | assert(((uintptr_t)addr & HUGEPAGE_MASK) == 0 && | ||
| 108 | (size & HUGEPAGE_MASK) == 0); | ||
| 109 | pages_nohuge(addr, size); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | static void | ||
| 114 | base_edata_init(size_t *extent_sn_next, edata_t *edata, void *addr, | ||
| 115 | size_t size) { | ||
| 116 | size_t sn; | ||
| 117 | |||
| 118 | sn = *extent_sn_next; | ||
| 119 | (*extent_sn_next)++; | ||
| 120 | |||
| 121 | edata_binit(edata, addr, size, sn); | ||
| 122 | } | ||
| 123 | |||
| 124 | static size_t | ||
| 125 | base_get_num_blocks(base_t *base, bool with_new_block) { | ||
| 126 | base_block_t *b = base->blocks; | ||
| 127 | assert(b != NULL); | ||
| 128 | |||
| 129 | size_t n_blocks = with_new_block ? 2 : 1; | ||
| 130 | while (b->next != NULL) { | ||
| 131 | n_blocks++; | ||
| 132 | b = b->next; | ||
| 133 | } | ||
| 134 | |||
| 135 | return n_blocks; | ||
| 136 | } | ||
| 137 | |||
| 138 | static void | ||
| 139 | base_auto_thp_switch(tsdn_t *tsdn, base_t *base) { | ||
| 140 | assert(opt_metadata_thp == metadata_thp_auto); | ||
| 141 | malloc_mutex_assert_owner(tsdn, &base->mtx); | ||
| 142 | if (base->auto_thp_switched) { | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | /* Called when adding a new block. */ | ||
| 146 | bool should_switch; | ||
| 147 | if (base_ind_get(base) != 0) { | ||
| 148 | should_switch = (base_get_num_blocks(base, true) == | ||
| 149 | BASE_AUTO_THP_THRESHOLD); | ||
| 150 | } else { | ||
| 151 | should_switch = (base_get_num_blocks(base, true) == | ||
| 152 | BASE_AUTO_THP_THRESHOLD_A0); | ||
| 153 | } | ||
| 154 | if (!should_switch) { | ||
| 155 | return; | ||
| 156 | } | ||
| 157 | |||
| 158 | base->auto_thp_switched = true; | ||
| 159 | assert(!config_stats || base->n_thp == 0); | ||
| 160 | /* Make the initial blocks THP lazily. */ | ||
| 161 | base_block_t *block = base->blocks; | ||
| 162 | while (block != NULL) { | ||
| 163 | assert((block->size & HUGEPAGE_MASK) == 0); | ||
| 164 | pages_huge(block, block->size); | ||
| 165 | if (config_stats) { | ||
| 166 | base->n_thp += HUGEPAGE_CEILING(block->size - | ||
| 167 | edata_bsize_get(&block->edata)) >> LG_HUGEPAGE; | ||
| 168 | } | ||
| 169 | block = block->next; | ||
| 170 | assert(block == NULL || (base_ind_get(base) == 0)); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | static void * | ||
| 175 | base_extent_bump_alloc_helper(edata_t *edata, size_t *gap_size, size_t size, | ||
| 176 | size_t alignment) { | ||
| 177 | void *ret; | ||
| 178 | |||
| 179 | assert(alignment == ALIGNMENT_CEILING(alignment, QUANTUM)); | ||
| 180 | assert(size == ALIGNMENT_CEILING(size, alignment)); | ||
| 181 | |||
| 182 | *gap_size = ALIGNMENT_CEILING((uintptr_t)edata_addr_get(edata), | ||
| 183 | alignment) - (uintptr_t)edata_addr_get(edata); | ||
| 184 | ret = (void *)((uintptr_t)edata_addr_get(edata) + *gap_size); | ||
| 185 | assert(edata_bsize_get(edata) >= *gap_size + size); | ||
| 186 | edata_binit(edata, (void *)((uintptr_t)edata_addr_get(edata) + | ||
| 187 | *gap_size + size), edata_bsize_get(edata) - *gap_size - size, | ||
| 188 | edata_sn_get(edata)); | ||
| 189 | return ret; | ||
| 190 | } | ||
| 191 | |||
| 192 | static void | ||
| 193 | base_extent_bump_alloc_post(base_t *base, edata_t *edata, size_t gap_size, | ||
| 194 | void *addr, size_t size) { | ||
| 195 | if (edata_bsize_get(edata) > 0) { | ||
| 196 | /* | ||
| 197 | * Compute the index for the largest size class that does not | ||
| 198 | * exceed extent's size. | ||
| 199 | */ | ||
| 200 | szind_t index_floor = | ||
| 201 | sz_size2index(edata_bsize_get(edata) + 1) - 1; | ||
| 202 | edata_heap_insert(&base->avail[index_floor], edata); | ||
| 203 | } | ||
| 204 | |||
| 205 | if (config_stats) { | ||
| 206 | base->allocated += size; | ||
| 207 | /* | ||
| 208 | * Add one PAGE to base_resident for every page boundary that is | ||
| 209 | * crossed by the new allocation. Adjust n_thp similarly when | ||
| 210 | * metadata_thp is enabled. | ||
| 211 | */ | ||
| 212 | base->resident += PAGE_CEILING((uintptr_t)addr + size) - | ||
| 213 | PAGE_CEILING((uintptr_t)addr - gap_size); | ||
| 214 | assert(base->allocated <= base->resident); | ||
| 215 | assert(base->resident <= base->mapped); | ||
| 216 | if (metadata_thp_madvise() && (opt_metadata_thp == | ||
| 217 | metadata_thp_always || base->auto_thp_switched)) { | ||
| 218 | base->n_thp += (HUGEPAGE_CEILING((uintptr_t)addr + size) | ||
| 219 | - HUGEPAGE_CEILING((uintptr_t)addr - gap_size)) >> | ||
| 220 | LG_HUGEPAGE; | ||
| 221 | assert(base->mapped >= base->n_thp << LG_HUGEPAGE); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | static void * | ||
| 227 | base_extent_bump_alloc(base_t *base, edata_t *edata, size_t size, | ||
| 228 | size_t alignment) { | ||
| 229 | void *ret; | ||
| 230 | size_t gap_size; | ||
| 231 | |||
| 232 | ret = base_extent_bump_alloc_helper(edata, &gap_size, size, alignment); | ||
| 233 | base_extent_bump_alloc_post(base, edata, gap_size, ret, size); | ||
| 234 | return ret; | ||
| 235 | } | ||
| 236 | |||
| 237 | /* | ||
| 238 | * Allocate a block of virtual memory that is large enough to start with a | ||
| 239 | * base_block_t header, followed by an object of specified size and alignment. | ||
| 240 | * On success a pointer to the initialized base_block_t header is returned. | ||
| 241 | */ | ||
| 242 | static base_block_t * | ||
| 243 | base_block_alloc(tsdn_t *tsdn, base_t *base, ehooks_t *ehooks, unsigned ind, | ||
| 244 | pszind_t *pind_last, size_t *extent_sn_next, size_t size, | ||
| 245 | size_t alignment) { | ||
| 246 | alignment = ALIGNMENT_CEILING(alignment, QUANTUM); | ||
| 247 | size_t usize = ALIGNMENT_CEILING(size, alignment); | ||
| 248 | size_t header_size = sizeof(base_block_t); | ||
| 249 | size_t gap_size = ALIGNMENT_CEILING(header_size, alignment) - | ||
| 250 | header_size; | ||
| 251 | /* | ||
| 252 | * Create increasingly larger blocks in order to limit the total number | ||
| 253 | * of disjoint virtual memory ranges. Choose the next size in the page | ||
| 254 | * size class series (skipping size classes that are not a multiple of | ||
| 255 | * HUGEPAGE), or a size large enough to satisfy the requested size and | ||
| 256 | * alignment, whichever is larger. | ||
| 257 | */ | ||
| 258 | size_t min_block_size = HUGEPAGE_CEILING(sz_psz2u(header_size + gap_size | ||
| 259 | + usize)); | ||
| 260 | pszind_t pind_next = (*pind_last + 1 < sz_psz2ind(SC_LARGE_MAXCLASS)) ? | ||
| 261 | *pind_last + 1 : *pind_last; | ||
| 262 | size_t next_block_size = HUGEPAGE_CEILING(sz_pind2sz(pind_next)); | ||
| 263 | size_t block_size = (min_block_size > next_block_size) ? min_block_size | ||
| 264 | : next_block_size; | ||
| 265 | base_block_t *block = (base_block_t *)base_map(tsdn, ehooks, ind, | ||
| 266 | block_size); | ||
| 267 | if (block == NULL) { | ||
| 268 | return NULL; | ||
| 269 | } | ||
| 270 | |||
| 271 | if (metadata_thp_madvise()) { | ||
| 272 | void *addr = (void *)block; | ||
| 273 | assert(((uintptr_t)addr & HUGEPAGE_MASK) == 0 && | ||
| 274 | (block_size & HUGEPAGE_MASK) == 0); | ||
| 275 | if (opt_metadata_thp == metadata_thp_always) { | ||
| 276 | pages_huge(addr, block_size); | ||
| 277 | } else if (opt_metadata_thp == metadata_thp_auto && | ||
| 278 | base != NULL) { | ||
| 279 | /* base != NULL indicates this is not a new base. */ | ||
| 280 | malloc_mutex_lock(tsdn, &base->mtx); | ||
| 281 | base_auto_thp_switch(tsdn, base); | ||
| 282 | if (base->auto_thp_switched) { | ||
| 283 | pages_huge(addr, block_size); | ||
| 284 | } | ||
| 285 | malloc_mutex_unlock(tsdn, &base->mtx); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | *pind_last = sz_psz2ind(block_size); | ||
| 290 | block->size = block_size; | ||
| 291 | block->next = NULL; | ||
| 292 | assert(block_size >= header_size); | ||
| 293 | base_edata_init(extent_sn_next, &block->edata, | ||
| 294 | (void *)((uintptr_t)block + header_size), block_size - header_size); | ||
| 295 | return block; | ||
| 296 | } | ||
| 297 | |||
| 298 | /* | ||
| 299 | * Allocate an extent that is at least as large as specified size, with | ||
| 300 | * specified alignment. | ||
| 301 | */ | ||
| 302 | static edata_t * | ||
| 303 | base_extent_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment) { | ||
| 304 | malloc_mutex_assert_owner(tsdn, &base->mtx); | ||
| 305 | |||
| 306 | ehooks_t *ehooks = base_ehooks_get_for_metadata(base); | ||
| 307 | /* | ||
| 308 | * Drop mutex during base_block_alloc(), because an extent hook will be | ||
| 309 | * called. | ||
| 310 | */ | ||
| 311 | malloc_mutex_unlock(tsdn, &base->mtx); | ||
| 312 | base_block_t *block = base_block_alloc(tsdn, base, ehooks, | ||
| 313 | base_ind_get(base), &base->pind_last, &base->extent_sn_next, size, | ||
| 314 | alignment); | ||
| 315 | malloc_mutex_lock(tsdn, &base->mtx); | ||
| 316 | if (block == NULL) { | ||
| 317 | return NULL; | ||
| 318 | } | ||
| 319 | block->next = base->blocks; | ||
| 320 | base->blocks = block; | ||
| 321 | if (config_stats) { | ||
| 322 | base->allocated += sizeof(base_block_t); | ||
| 323 | base->resident += PAGE_CEILING(sizeof(base_block_t)); | ||
| 324 | base->mapped += block->size; | ||
| 325 | if (metadata_thp_madvise() && | ||
| 326 | !(opt_metadata_thp == metadata_thp_auto | ||
| 327 | && !base->auto_thp_switched)) { | ||
| 328 | assert(base->n_thp > 0); | ||
| 329 | base->n_thp += HUGEPAGE_CEILING(sizeof(base_block_t)) >> | ||
| 330 | LG_HUGEPAGE; | ||
| 331 | } | ||
| 332 | assert(base->allocated <= base->resident); | ||
| 333 | assert(base->resident <= base->mapped); | ||
| 334 | assert(base->n_thp << LG_HUGEPAGE <= base->mapped); | ||
| 335 | } | ||
| 336 | return &block->edata; | ||
| 337 | } | ||
| 338 | |||
| 339 | base_t * | ||
| 340 | b0get(void) { | ||
| 341 | return b0; | ||
| 342 | } | ||
| 343 | |||
| 344 | base_t * | ||
| 345 | base_new(tsdn_t *tsdn, unsigned ind, const extent_hooks_t *extent_hooks, | ||
| 346 | bool metadata_use_hooks) { | ||
| 347 | pszind_t pind_last = 0; | ||
| 348 | size_t extent_sn_next = 0; | ||
| 349 | |||
| 350 | /* | ||
| 351 | * The base will contain the ehooks eventually, but it itself is | ||
| 352 | * allocated using them. So we use some stack ehooks to bootstrap its | ||
| 353 | * memory, and then initialize the ehooks within the base_t. | ||
| 354 | */ | ||
| 355 | ehooks_t fake_ehooks; | ||
| 356 | ehooks_init(&fake_ehooks, metadata_use_hooks ? | ||
| 357 | (extent_hooks_t *)extent_hooks : | ||
| 358 | (extent_hooks_t *)&ehooks_default_extent_hooks, ind); | ||
| 359 | |||
| 360 | base_block_t *block = base_block_alloc(tsdn, NULL, &fake_ehooks, ind, | ||
| 361 | &pind_last, &extent_sn_next, sizeof(base_t), QUANTUM); | ||
| 362 | if (block == NULL) { | ||
| 363 | return NULL; | ||
| 364 | } | ||
| 365 | |||
| 366 | size_t gap_size; | ||
| 367 | size_t base_alignment = CACHELINE; | ||
| 368 | size_t base_size = ALIGNMENT_CEILING(sizeof(base_t), base_alignment); | ||
| 369 | base_t *base = (base_t *)base_extent_bump_alloc_helper(&block->edata, | ||
| 370 | &gap_size, base_size, base_alignment); | ||
| 371 | ehooks_init(&base->ehooks, (extent_hooks_t *)extent_hooks, ind); | ||
| 372 | ehooks_init(&base->ehooks_base, metadata_use_hooks ? | ||
| 373 | (extent_hooks_t *)extent_hooks : | ||
| 374 | (extent_hooks_t *)&ehooks_default_extent_hooks, ind); | ||
| 375 | if (malloc_mutex_init(&base->mtx, "base", WITNESS_RANK_BASE, | ||
| 376 | malloc_mutex_rank_exclusive)) { | ||
| 377 | base_unmap(tsdn, &fake_ehooks, ind, block, block->size); | ||
| 378 | return NULL; | ||
| 379 | } | ||
| 380 | base->pind_last = pind_last; | ||
| 381 | base->extent_sn_next = extent_sn_next; | ||
| 382 | base->blocks = block; | ||
| 383 | base->auto_thp_switched = false; | ||
| 384 | for (szind_t i = 0; i < SC_NSIZES; i++) { | ||
| 385 | edata_heap_new(&base->avail[i]); | ||
| 386 | } | ||
| 387 | if (config_stats) { | ||
| 388 | base->allocated = sizeof(base_block_t); | ||
| 389 | base->resident = PAGE_CEILING(sizeof(base_block_t)); | ||
| 390 | base->mapped = block->size; | ||
| 391 | base->n_thp = (opt_metadata_thp == metadata_thp_always) && | ||
| 392 | metadata_thp_madvise() ? HUGEPAGE_CEILING(sizeof(base_block_t)) | ||
| 393 | >> LG_HUGEPAGE : 0; | ||
| 394 | assert(base->allocated <= base->resident); | ||
| 395 | assert(base->resident <= base->mapped); | ||
| 396 | assert(base->n_thp << LG_HUGEPAGE <= base->mapped); | ||
| 397 | } | ||
| 398 | base_extent_bump_alloc_post(base, &block->edata, gap_size, base, | ||
| 399 | base_size); | ||
| 400 | |||
| 401 | return base; | ||
| 402 | } | ||
| 403 | |||
| 404 | void | ||
| 405 | base_delete(tsdn_t *tsdn, base_t *base) { | ||
| 406 | ehooks_t *ehooks = base_ehooks_get_for_metadata(base); | ||
| 407 | base_block_t *next = base->blocks; | ||
| 408 | do { | ||
| 409 | base_block_t *block = next; | ||
| 410 | next = block->next; | ||
| 411 | base_unmap(tsdn, ehooks, base_ind_get(base), block, | ||
| 412 | block->size); | ||
| 413 | } while (next != NULL); | ||
| 414 | } | ||
| 415 | |||
| 416 | ehooks_t * | ||
| 417 | base_ehooks_get(base_t *base) { | ||
| 418 | return &base->ehooks; | ||
| 419 | } | ||
| 420 | |||
| 421 | ehooks_t * | ||
| 422 | base_ehooks_get_for_metadata(base_t *base) { | ||
| 423 | return &base->ehooks_base; | ||
| 424 | } | ||
| 425 | |||
| 426 | extent_hooks_t * | ||
| 427 | base_extent_hooks_set(base_t *base, extent_hooks_t *extent_hooks) { | ||
| 428 | extent_hooks_t *old_extent_hooks = | ||
| 429 | ehooks_get_extent_hooks_ptr(&base->ehooks); | ||
| 430 | ehooks_init(&base->ehooks, extent_hooks, ehooks_ind_get(&base->ehooks)); | ||
| 431 | return old_extent_hooks; | ||
| 432 | } | ||
| 433 | |||
| 434 | static void * | ||
| 435 | base_alloc_impl(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment, | ||
| 436 | size_t *esn) { | ||
| 437 | alignment = QUANTUM_CEILING(alignment); | ||
| 438 | size_t usize = ALIGNMENT_CEILING(size, alignment); | ||
| 439 | size_t asize = usize + alignment - QUANTUM; | ||
| 440 | |||
| 441 | edata_t *edata = NULL; | ||
| 442 | malloc_mutex_lock(tsdn, &base->mtx); | ||
| 443 | for (szind_t i = sz_size2index(asize); i < SC_NSIZES; i++) { | ||
| 444 | edata = edata_heap_remove_first(&base->avail[i]); | ||
| 445 | if (edata != NULL) { | ||
| 446 | /* Use existing space. */ | ||
| 447 | break; | ||
| 448 | } | ||
| 449 | } | ||
| 450 | if (edata == NULL) { | ||
| 451 | /* Try to allocate more space. */ | ||
| 452 | edata = base_extent_alloc(tsdn, base, usize, alignment); | ||
| 453 | } | ||
| 454 | void *ret; | ||
| 455 | if (edata == NULL) { | ||
| 456 | ret = NULL; | ||
| 457 | goto label_return; | ||
| 458 | } | ||
| 459 | |||
| 460 | ret = base_extent_bump_alloc(base, edata, usize, alignment); | ||
| 461 | if (esn != NULL) { | ||
| 462 | *esn = (size_t)edata_sn_get(edata); | ||
| 463 | } | ||
| 464 | label_return: | ||
| 465 | malloc_mutex_unlock(tsdn, &base->mtx); | ||
| 466 | return ret; | ||
| 467 | } | ||
| 468 | |||
| 469 | /* | ||
| 470 | * base_alloc() returns zeroed memory, which is always demand-zeroed for the | ||
| 471 | * auto arenas, in order to make multi-page sparse data structures such as radix | ||
| 472 | * tree nodes efficient with respect to physical memory usage. Upon success a | ||
| 473 | * pointer to at least size bytes with specified alignment is returned. Note | ||
| 474 | * that size is rounded up to the nearest multiple of alignment to avoid false | ||
| 475 | * sharing. | ||
| 476 | */ | ||
| 477 | void * | ||
| 478 | base_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment) { | ||
| 479 | return base_alloc_impl(tsdn, base, size, alignment, NULL); | ||
| 480 | } | ||
| 481 | |||
| 482 | edata_t * | ||
| 483 | base_alloc_edata(tsdn_t *tsdn, base_t *base) { | ||
| 484 | size_t esn; | ||
| 485 | edata_t *edata = base_alloc_impl(tsdn, base, sizeof(edata_t), | ||
| 486 | EDATA_ALIGNMENT, &esn); | ||
| 487 | if (edata == NULL) { | ||
| 488 | return NULL; | ||
| 489 | } | ||
| 490 | edata_esn_set(edata, esn); | ||
| 491 | return edata; | ||
| 492 | } | ||
| 493 | |||
| 494 | void | ||
| 495 | base_stats_get(tsdn_t *tsdn, base_t *base, size_t *allocated, size_t *resident, | ||
| 496 | size_t *mapped, size_t *n_thp) { | ||
| 497 | cassert(config_stats); | ||
| 498 | |||
| 499 | malloc_mutex_lock(tsdn, &base->mtx); | ||
| 500 | assert(base->allocated <= base->resident); | ||
| 501 | assert(base->resident <= base->mapped); | ||
| 502 | *allocated = base->allocated; | ||
| 503 | *resident = base->resident; | ||
| 504 | *mapped = base->mapped; | ||
| 505 | *n_thp = base->n_thp; | ||
| 506 | malloc_mutex_unlock(tsdn, &base->mtx); | ||
| 507 | } | ||
| 508 | |||
| 509 | void | ||
| 510 | base_prefork(tsdn_t *tsdn, base_t *base) { | ||
| 511 | malloc_mutex_prefork(tsdn, &base->mtx); | ||
| 512 | } | ||
| 513 | |||
| 514 | void | ||
| 515 | base_postfork_parent(tsdn_t *tsdn, base_t *base) { | ||
| 516 | malloc_mutex_postfork_parent(tsdn, &base->mtx); | ||
| 517 | } | ||
| 518 | |||
| 519 | void | ||
| 520 | base_postfork_child(tsdn_t *tsdn, base_t *base) { | ||
| 521 | malloc_mutex_postfork_child(tsdn, &base->mtx); | ||
| 522 | } | ||
| 523 | |||
| 524 | bool | ||
| 525 | base_boot(tsdn_t *tsdn) { | ||
| 526 | b0 = base_new(tsdn, 0, (extent_hooks_t *)&ehooks_default_extent_hooks, | ||
| 527 | /* metadata_use_hooks */ true); | ||
| 528 | return (b0 == NULL); | ||
| 529 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/bin.c b/examples/redis-unstable/deps/jemalloc/src/bin.c deleted file mode 100644 index fa20458..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/bin.c +++ /dev/null | |||
| @@ -1,69 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/bin.h" | ||
| 6 | #include "jemalloc/internal/sc.h" | ||
| 7 | #include "jemalloc/internal/witness.h" | ||
| 8 | |||
| 9 | bool | ||
| 10 | bin_update_shard_size(unsigned bin_shard_sizes[SC_NBINS], size_t start_size, | ||
| 11 | size_t end_size, size_t nshards) { | ||
| 12 | if (nshards > BIN_SHARDS_MAX || nshards == 0) { | ||
| 13 | return true; | ||
| 14 | } | ||
| 15 | |||
| 16 | if (start_size > SC_SMALL_MAXCLASS) { | ||
| 17 | return false; | ||
| 18 | } | ||
| 19 | if (end_size > SC_SMALL_MAXCLASS) { | ||
| 20 | end_size = SC_SMALL_MAXCLASS; | ||
| 21 | } | ||
| 22 | |||
| 23 | /* Compute the index since this may happen before sz init. */ | ||
| 24 | szind_t ind1 = sz_size2index_compute(start_size); | ||
| 25 | szind_t ind2 = sz_size2index_compute(end_size); | ||
| 26 | for (unsigned i = ind1; i <= ind2; i++) { | ||
| 27 | bin_shard_sizes[i] = (unsigned)nshards; | ||
| 28 | } | ||
| 29 | |||
| 30 | return false; | ||
| 31 | } | ||
| 32 | |||
| 33 | void | ||
| 34 | bin_shard_sizes_boot(unsigned bin_shard_sizes[SC_NBINS]) { | ||
| 35 | /* Load the default number of shards. */ | ||
| 36 | for (unsigned i = 0; i < SC_NBINS; i++) { | ||
| 37 | bin_shard_sizes[i] = N_BIN_SHARDS_DEFAULT; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | bool | ||
| 42 | bin_init(bin_t *bin) { | ||
| 43 | if (malloc_mutex_init(&bin->lock, "bin", WITNESS_RANK_BIN, | ||
| 44 | malloc_mutex_rank_exclusive)) { | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | bin->slabcur = NULL; | ||
| 48 | edata_heap_new(&bin->slabs_nonfull); | ||
| 49 | edata_list_active_init(&bin->slabs_full); | ||
| 50 | if (config_stats) { | ||
| 51 | memset(&bin->stats, 0, sizeof(bin_stats_t)); | ||
| 52 | } | ||
| 53 | return false; | ||
| 54 | } | ||
| 55 | |||
| 56 | void | ||
| 57 | bin_prefork(tsdn_t *tsdn, bin_t *bin) { | ||
| 58 | malloc_mutex_prefork(tsdn, &bin->lock); | ||
| 59 | } | ||
| 60 | |||
| 61 | void | ||
| 62 | bin_postfork_parent(tsdn_t *tsdn, bin_t *bin) { | ||
| 63 | malloc_mutex_postfork_parent(tsdn, &bin->lock); | ||
| 64 | } | ||
| 65 | |||
| 66 | void | ||
| 67 | bin_postfork_child(tsdn_t *tsdn, bin_t *bin) { | ||
| 68 | malloc_mutex_postfork_child(tsdn, &bin->lock); | ||
| 69 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/bin_info.c b/examples/redis-unstable/deps/jemalloc/src/bin_info.c deleted file mode 100644 index 8629ef8..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/bin_info.c +++ /dev/null | |||
| @@ -1,30 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/bin_info.h" | ||
| 5 | |||
| 6 | bin_info_t bin_infos[SC_NBINS]; | ||
| 7 | |||
| 8 | static void | ||
| 9 | bin_infos_init(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS], | ||
| 10 | bin_info_t infos[SC_NBINS]) { | ||
| 11 | for (unsigned i = 0; i < SC_NBINS; i++) { | ||
| 12 | bin_info_t *bin_info = &infos[i]; | ||
| 13 | sc_t *sc = &sc_data->sc[i]; | ||
| 14 | bin_info->reg_size = ((size_t)1U << sc->lg_base) | ||
| 15 | + ((size_t)sc->ndelta << sc->lg_delta); | ||
| 16 | bin_info->slab_size = (sc->pgs << LG_PAGE); | ||
| 17 | bin_info->nregs = | ||
| 18 | (uint32_t)(bin_info->slab_size / bin_info->reg_size); | ||
| 19 | bin_info->n_shards = bin_shard_sizes[i]; | ||
| 20 | bitmap_info_t bitmap_info = BITMAP_INFO_INITIALIZER( | ||
| 21 | bin_info->nregs); | ||
| 22 | bin_info->bitmap_info = bitmap_info; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | void | ||
| 27 | bin_info_boot(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS]) { | ||
| 28 | assert(sc_data->initialized); | ||
| 29 | bin_infos_init(sc_data, bin_shard_sizes, bin_infos); | ||
| 30 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/bitmap.c b/examples/redis-unstable/deps/jemalloc/src/bitmap.c deleted file mode 100644 index 0ccedc5..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/bitmap.c +++ /dev/null | |||
| @@ -1,120 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | |||
| 6 | /******************************************************************************/ | ||
| 7 | |||
| 8 | #ifdef BITMAP_USE_TREE | ||
| 9 | |||
| 10 | void | ||
| 11 | bitmap_info_init(bitmap_info_t *binfo, size_t nbits) { | ||
| 12 | unsigned i; | ||
| 13 | size_t group_count; | ||
| 14 | |||
| 15 | assert(nbits > 0); | ||
| 16 | assert(nbits <= (ZU(1) << LG_BITMAP_MAXBITS)); | ||
| 17 | |||
| 18 | /* | ||
| 19 | * Compute the number of groups necessary to store nbits bits, and | ||
| 20 | * progressively work upward through the levels until reaching a level | ||
| 21 | * that requires only one group. | ||
| 22 | */ | ||
| 23 | binfo->levels[0].group_offset = 0; | ||
| 24 | group_count = BITMAP_BITS2GROUPS(nbits); | ||
| 25 | for (i = 1; group_count > 1; i++) { | ||
| 26 | assert(i < BITMAP_MAX_LEVELS); | ||
| 27 | binfo->levels[i].group_offset = binfo->levels[i-1].group_offset | ||
| 28 | + group_count; | ||
| 29 | group_count = BITMAP_BITS2GROUPS(group_count); | ||
| 30 | } | ||
| 31 | binfo->levels[i].group_offset = binfo->levels[i-1].group_offset | ||
| 32 | + group_count; | ||
| 33 | assert(binfo->levels[i].group_offset <= BITMAP_GROUPS_MAX); | ||
| 34 | binfo->nlevels = i; | ||
| 35 | binfo->nbits = nbits; | ||
| 36 | } | ||
| 37 | |||
| 38 | static size_t | ||
| 39 | bitmap_info_ngroups(const bitmap_info_t *binfo) { | ||
| 40 | return binfo->levels[binfo->nlevels].group_offset; | ||
| 41 | } | ||
| 42 | |||
| 43 | void | ||
| 44 | bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill) { | ||
| 45 | size_t extra; | ||
| 46 | unsigned i; | ||
| 47 | |||
| 48 | /* | ||
| 49 | * Bits are actually inverted with regard to the external bitmap | ||
| 50 | * interface. | ||
| 51 | */ | ||
| 52 | |||
| 53 | if (fill) { | ||
| 54 | /* The "filled" bitmap starts out with all 0 bits. */ | ||
| 55 | memset(bitmap, 0, bitmap_size(binfo)); | ||
| 56 | return; | ||
| 57 | } | ||
| 58 | |||
| 59 | /* | ||
| 60 | * The "empty" bitmap starts out with all 1 bits, except for trailing | ||
| 61 | * unused bits (if any). Note that each group uses bit 0 to correspond | ||
| 62 | * to the first logical bit in the group, so extra bits are the most | ||
| 63 | * significant bits of the last group. | ||
| 64 | */ | ||
| 65 | memset(bitmap, 0xffU, bitmap_size(binfo)); | ||
| 66 | extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK)) | ||
| 67 | & BITMAP_GROUP_NBITS_MASK; | ||
| 68 | if (extra != 0) { | ||
| 69 | bitmap[binfo->levels[1].group_offset - 1] >>= extra; | ||
| 70 | } | ||
| 71 | for (i = 1; i < binfo->nlevels; i++) { | ||
| 72 | size_t group_count = binfo->levels[i].group_offset - | ||
| 73 | binfo->levels[i-1].group_offset; | ||
| 74 | extra = (BITMAP_GROUP_NBITS - (group_count & | ||
| 75 | BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK; | ||
| 76 | if (extra != 0) { | ||
| 77 | bitmap[binfo->levels[i+1].group_offset - 1] >>= extra; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | #else /* BITMAP_USE_TREE */ | ||
| 83 | |||
| 84 | void | ||
| 85 | bitmap_info_init(bitmap_info_t *binfo, size_t nbits) { | ||
| 86 | assert(nbits > 0); | ||
| 87 | assert(nbits <= (ZU(1) << LG_BITMAP_MAXBITS)); | ||
| 88 | |||
| 89 | binfo->ngroups = BITMAP_BITS2GROUPS(nbits); | ||
| 90 | binfo->nbits = nbits; | ||
| 91 | } | ||
| 92 | |||
| 93 | static size_t | ||
| 94 | bitmap_info_ngroups(const bitmap_info_t *binfo) { | ||
| 95 | return binfo->ngroups; | ||
| 96 | } | ||
| 97 | |||
| 98 | void | ||
| 99 | bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill) { | ||
| 100 | size_t extra; | ||
| 101 | |||
| 102 | if (fill) { | ||
| 103 | memset(bitmap, 0, bitmap_size(binfo)); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | |||
| 107 | memset(bitmap, 0xffU, bitmap_size(binfo)); | ||
| 108 | extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK)) | ||
| 109 | & BITMAP_GROUP_NBITS_MASK; | ||
| 110 | if (extra != 0) { | ||
| 111 | bitmap[binfo->ngroups - 1] >>= extra; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | #endif /* BITMAP_USE_TREE */ | ||
| 116 | |||
| 117 | size_t | ||
| 118 | bitmap_size(const bitmap_info_t *binfo) { | ||
| 119 | return (bitmap_info_ngroups(binfo) << LG_SIZEOF_BITMAP); | ||
| 120 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/buf_writer.c b/examples/redis-unstable/deps/jemalloc/src/buf_writer.c deleted file mode 100644 index 7c6f794..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/buf_writer.c +++ /dev/null | |||
| @@ -1,144 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/buf_writer.h" | ||
| 5 | #include "jemalloc/internal/malloc_io.h" | ||
| 6 | |||
| 7 | static void * | ||
| 8 | buf_writer_allocate_internal_buf(tsdn_t *tsdn, size_t buf_len) { | ||
| 9 | #ifdef JEMALLOC_JET | ||
| 10 | if (buf_len > SC_LARGE_MAXCLASS) { | ||
| 11 | return NULL; | ||
| 12 | } | ||
| 13 | #else | ||
| 14 | assert(buf_len <= SC_LARGE_MAXCLASS); | ||
| 15 | #endif | ||
| 16 | return iallocztm(tsdn, buf_len, sz_size2index(buf_len), false, NULL, | ||
| 17 | true, arena_get(tsdn, 0, false), true); | ||
| 18 | } | ||
| 19 | |||
| 20 | static void | ||
| 21 | buf_writer_free_internal_buf(tsdn_t *tsdn, void *buf) { | ||
| 22 | if (buf != NULL) { | ||
| 23 | idalloctm(tsdn, buf, NULL, NULL, true, true); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | static void | ||
| 28 | buf_writer_assert(buf_writer_t *buf_writer) { | ||
| 29 | assert(buf_writer != NULL); | ||
| 30 | assert(buf_writer->write_cb != NULL); | ||
| 31 | if (buf_writer->buf != NULL) { | ||
| 32 | assert(buf_writer->buf_size > 0); | ||
| 33 | } else { | ||
| 34 | assert(buf_writer->buf_size == 0); | ||
| 35 | assert(buf_writer->internal_buf); | ||
| 36 | } | ||
| 37 | assert(buf_writer->buf_end <= buf_writer->buf_size); | ||
| 38 | } | ||
| 39 | |||
| 40 | bool | ||
| 41 | buf_writer_init(tsdn_t *tsdn, buf_writer_t *buf_writer, write_cb_t *write_cb, | ||
| 42 | void *cbopaque, char *buf, size_t buf_len) { | ||
| 43 | if (write_cb != NULL) { | ||
| 44 | buf_writer->write_cb = write_cb; | ||
| 45 | } else { | ||
| 46 | buf_writer->write_cb = je_malloc_message != NULL ? | ||
| 47 | je_malloc_message : wrtmessage; | ||
| 48 | } | ||
| 49 | buf_writer->cbopaque = cbopaque; | ||
| 50 | assert(buf_len >= 2); | ||
| 51 | if (buf != NULL) { | ||
| 52 | buf_writer->buf = buf; | ||
| 53 | buf_writer->internal_buf = false; | ||
| 54 | } else { | ||
| 55 | buf_writer->buf = buf_writer_allocate_internal_buf(tsdn, | ||
| 56 | buf_len); | ||
| 57 | buf_writer->internal_buf = true; | ||
| 58 | } | ||
| 59 | if (buf_writer->buf != NULL) { | ||
| 60 | buf_writer->buf_size = buf_len - 1; /* Allowing for '\0'. */ | ||
| 61 | } else { | ||
| 62 | buf_writer->buf_size = 0; | ||
| 63 | } | ||
| 64 | buf_writer->buf_end = 0; | ||
| 65 | buf_writer_assert(buf_writer); | ||
| 66 | return buf_writer->buf == NULL; | ||
| 67 | } | ||
| 68 | |||
| 69 | void | ||
| 70 | buf_writer_flush(buf_writer_t *buf_writer) { | ||
| 71 | buf_writer_assert(buf_writer); | ||
| 72 | if (buf_writer->buf == NULL) { | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | buf_writer->buf[buf_writer->buf_end] = '\0'; | ||
| 76 | buf_writer->write_cb(buf_writer->cbopaque, buf_writer->buf); | ||
| 77 | buf_writer->buf_end = 0; | ||
| 78 | buf_writer_assert(buf_writer); | ||
| 79 | } | ||
| 80 | |||
| 81 | void | ||
| 82 | buf_writer_cb(void *buf_writer_arg, const char *s) { | ||
| 83 | buf_writer_t *buf_writer = (buf_writer_t *)buf_writer_arg; | ||
| 84 | buf_writer_assert(buf_writer); | ||
| 85 | if (buf_writer->buf == NULL) { | ||
| 86 | buf_writer->write_cb(buf_writer->cbopaque, s); | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | size_t i, slen, n; | ||
| 90 | for (i = 0, slen = strlen(s); i < slen; i += n) { | ||
| 91 | if (buf_writer->buf_end == buf_writer->buf_size) { | ||
| 92 | buf_writer_flush(buf_writer); | ||
| 93 | } | ||
| 94 | size_t s_remain = slen - i; | ||
| 95 | size_t buf_remain = buf_writer->buf_size - buf_writer->buf_end; | ||
| 96 | n = s_remain < buf_remain ? s_remain : buf_remain; | ||
| 97 | memcpy(buf_writer->buf + buf_writer->buf_end, s + i, n); | ||
| 98 | buf_writer->buf_end += n; | ||
| 99 | buf_writer_assert(buf_writer); | ||
| 100 | } | ||
| 101 | assert(i == slen); | ||
| 102 | } | ||
| 103 | |||
| 104 | void | ||
| 105 | buf_writer_terminate(tsdn_t *tsdn, buf_writer_t *buf_writer) { | ||
| 106 | buf_writer_assert(buf_writer); | ||
| 107 | buf_writer_flush(buf_writer); | ||
| 108 | if (buf_writer->internal_buf) { | ||
| 109 | buf_writer_free_internal_buf(tsdn, buf_writer->buf); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | void | ||
| 114 | buf_writer_pipe(buf_writer_t *buf_writer, read_cb_t *read_cb, | ||
| 115 | void *read_cbopaque) { | ||
| 116 | /* | ||
| 117 | * A tiny local buffer in case the buffered writer failed to allocate | ||
| 118 | * at init. | ||
| 119 | */ | ||
| 120 | static char backup_buf[16]; | ||
| 121 | static buf_writer_t backup_buf_writer; | ||
| 122 | |||
| 123 | buf_writer_assert(buf_writer); | ||
| 124 | assert(read_cb != NULL); | ||
| 125 | if (buf_writer->buf == NULL) { | ||
| 126 | buf_writer_init(TSDN_NULL, &backup_buf_writer, | ||
| 127 | buf_writer->write_cb, buf_writer->cbopaque, backup_buf, | ||
| 128 | sizeof(backup_buf)); | ||
| 129 | buf_writer = &backup_buf_writer; | ||
| 130 | } | ||
| 131 | assert(buf_writer->buf != NULL); | ||
| 132 | ssize_t nread = 0; | ||
| 133 | do { | ||
| 134 | buf_writer->buf_end += nread; | ||
| 135 | buf_writer_assert(buf_writer); | ||
| 136 | if (buf_writer->buf_end == buf_writer->buf_size) { | ||
| 137 | buf_writer_flush(buf_writer); | ||
| 138 | } | ||
| 139 | nread = read_cb(read_cbopaque, | ||
| 140 | buf_writer->buf + buf_writer->buf_end, | ||
| 141 | buf_writer->buf_size - buf_writer->buf_end); | ||
| 142 | } while (nread > 0); | ||
| 143 | buf_writer_flush(buf_writer); | ||
| 144 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/cache_bin.c b/examples/redis-unstable/deps/jemalloc/src/cache_bin.c deleted file mode 100644 index 9ae072a..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/cache_bin.c +++ /dev/null | |||
| @@ -1,99 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/bit_util.h" | ||
| 5 | #include "jemalloc/internal/cache_bin.h" | ||
| 6 | #include "jemalloc/internal/safety_check.h" | ||
| 7 | |||
| 8 | void | ||
| 9 | cache_bin_info_init(cache_bin_info_t *info, | ||
| 10 | cache_bin_sz_t ncached_max) { | ||
| 11 | assert(ncached_max <= CACHE_BIN_NCACHED_MAX); | ||
| 12 | size_t stack_size = (size_t)ncached_max * sizeof(void *); | ||
| 13 | assert(stack_size < ((size_t)1 << (sizeof(cache_bin_sz_t) * 8))); | ||
| 14 | info->ncached_max = (cache_bin_sz_t)ncached_max; | ||
| 15 | } | ||
| 16 | |||
| 17 | void | ||
| 18 | cache_bin_info_compute_alloc(cache_bin_info_t *infos, szind_t ninfos, | ||
| 19 | size_t *size, size_t *alignment) { | ||
| 20 | /* For the total bin stack region (per tcache), reserve 2 more slots so | ||
| 21 | * that | ||
| 22 | * 1) the empty position can be safely read on the fast path before | ||
| 23 | * checking "is_empty"; and | ||
| 24 | * 2) the cur_ptr can go beyond the empty position by 1 step safely on | ||
| 25 | * the fast path (i.e. no overflow). | ||
| 26 | */ | ||
| 27 | *size = sizeof(void *) * 2; | ||
| 28 | for (szind_t i = 0; i < ninfos; i++) { | ||
| 29 | assert(infos[i].ncached_max > 0); | ||
| 30 | *size += infos[i].ncached_max * sizeof(void *); | ||
| 31 | } | ||
| 32 | |||
| 33 | /* | ||
| 34 | * Align to at least PAGE, to minimize the # of TLBs needed by the | ||
| 35 | * smaller sizes; also helps if the larger sizes don't get used at all. | ||
| 36 | */ | ||
| 37 | *alignment = PAGE; | ||
| 38 | } | ||
| 39 | |||
| 40 | void | ||
| 41 | cache_bin_preincrement(cache_bin_info_t *infos, szind_t ninfos, void *alloc, | ||
| 42 | size_t *cur_offset) { | ||
| 43 | if (config_debug) { | ||
| 44 | size_t computed_size; | ||
| 45 | size_t computed_alignment; | ||
| 46 | |||
| 47 | /* Pointer should be as aligned as we asked for. */ | ||
| 48 | cache_bin_info_compute_alloc(infos, ninfos, &computed_size, | ||
| 49 | &computed_alignment); | ||
| 50 | assert(((uintptr_t)alloc & (computed_alignment - 1)) == 0); | ||
| 51 | } | ||
| 52 | |||
| 53 | *(uintptr_t *)((uintptr_t)alloc + *cur_offset) = | ||
| 54 | cache_bin_preceding_junk; | ||
| 55 | *cur_offset += sizeof(void *); | ||
| 56 | } | ||
| 57 | |||
| 58 | void | ||
| 59 | cache_bin_postincrement(cache_bin_info_t *infos, szind_t ninfos, void *alloc, | ||
| 60 | size_t *cur_offset) { | ||
| 61 | *(uintptr_t *)((uintptr_t)alloc + *cur_offset) = | ||
| 62 | cache_bin_trailing_junk; | ||
| 63 | *cur_offset += sizeof(void *); | ||
| 64 | } | ||
| 65 | |||
| 66 | void | ||
| 67 | cache_bin_init(cache_bin_t *bin, cache_bin_info_t *info, void *alloc, | ||
| 68 | size_t *cur_offset) { | ||
| 69 | /* | ||
| 70 | * The full_position points to the lowest available space. Allocations | ||
| 71 | * will access the slots toward higher addresses (for the benefit of | ||
| 72 | * adjacent prefetch). | ||
| 73 | */ | ||
| 74 | void *stack_cur = (void *)((uintptr_t)alloc + *cur_offset); | ||
| 75 | void *full_position = stack_cur; | ||
| 76 | uint16_t bin_stack_size = info->ncached_max * sizeof(void *); | ||
| 77 | |||
| 78 | *cur_offset += bin_stack_size; | ||
| 79 | void *empty_position = (void *)((uintptr_t)alloc + *cur_offset); | ||
| 80 | |||
| 81 | /* Init to the empty position. */ | ||
| 82 | bin->stack_head = (void **)empty_position; | ||
| 83 | bin->low_bits_low_water = (uint16_t)(uintptr_t)bin->stack_head; | ||
| 84 | bin->low_bits_full = (uint16_t)(uintptr_t)full_position; | ||
| 85 | bin->low_bits_empty = (uint16_t)(uintptr_t)empty_position; | ||
| 86 | cache_bin_sz_t free_spots = cache_bin_diff(bin, | ||
| 87 | bin->low_bits_full, (uint16_t)(uintptr_t)bin->stack_head, | ||
| 88 | /* racy */ false); | ||
| 89 | assert(free_spots == bin_stack_size); | ||
| 90 | assert(cache_bin_ncached_get_local(bin, info) == 0); | ||
| 91 | assert(cache_bin_empty_position_get(bin) == empty_position); | ||
| 92 | |||
| 93 | assert(bin_stack_size > 0 || empty_position == full_position); | ||
| 94 | } | ||
| 95 | |||
| 96 | bool | ||
| 97 | cache_bin_still_zero_initialized(cache_bin_t *bin) { | ||
| 98 | return bin->stack_head == NULL; | ||
| 99 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/ckh.c b/examples/redis-unstable/deps/jemalloc/src/ckh.c deleted file mode 100644 index 8db4319..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/ckh.c +++ /dev/null | |||
| @@ -1,569 +0,0 @@ | |||
| 1 | /* | ||
| 2 | ******************************************************************************* | ||
| 3 | * Implementation of (2^1+,2) cuckoo hashing, where 2^1+ indicates that each | ||
| 4 | * hash bucket contains 2^n cells, for n >= 1, and 2 indicates that two hash | ||
| 5 | * functions are employed. The original cuckoo hashing algorithm was described | ||
| 6 | * in: | ||
| 7 | * | ||
| 8 | * Pagh, R., F.F. Rodler (2004) Cuckoo Hashing. Journal of Algorithms | ||
| 9 | * 51(2):122-144. | ||
| 10 | * | ||
| 11 | * Generalization of cuckoo hashing was discussed in: | ||
| 12 | * | ||
| 13 | * Erlingsson, U., M. Manasse, F. McSherry (2006) A cool and practical | ||
| 14 | * alternative to traditional hash tables. In Proceedings of the 7th | ||
| 15 | * Workshop on Distributed Data and Structures (WDAS'06), Santa Clara, CA, | ||
| 16 | * January 2006. | ||
| 17 | * | ||
| 18 | * This implementation uses precisely two hash functions because that is the | ||
| 19 | * fewest that can work, and supporting multiple hashes is an implementation | ||
| 20 | * burden. Here is a reproduction of Figure 1 from Erlingsson et al. (2006) | ||
| 21 | * that shows approximate expected maximum load factors for various | ||
| 22 | * configurations: | ||
| 23 | * | ||
| 24 | * | #cells/bucket | | ||
| 25 | * #hashes | 1 | 2 | 4 | 8 | | ||
| 26 | * --------+-------+-------+-------+-------+ | ||
| 27 | * 1 | 0.006 | 0.006 | 0.03 | 0.12 | | ||
| 28 | * 2 | 0.49 | 0.86 |>0.93< |>0.96< | | ||
| 29 | * 3 | 0.91 | 0.97 | 0.98 | 0.999 | | ||
| 30 | * 4 | 0.97 | 0.99 | 0.999 | | | ||
| 31 | * | ||
| 32 | * The number of cells per bucket is chosen such that a bucket fits in one cache | ||
| 33 | * line. So, on 32- and 64-bit systems, we use (8,2) and (4,2) cuckoo hashing, | ||
| 34 | * respectively. | ||
| 35 | * | ||
| 36 | ******************************************************************************/ | ||
| 37 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 38 | |||
| 39 | #include "jemalloc/internal/ckh.h" | ||
| 40 | |||
| 41 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 42 | |||
| 43 | #include "jemalloc/internal/assert.h" | ||
| 44 | #include "jemalloc/internal/hash.h" | ||
| 45 | #include "jemalloc/internal/malloc_io.h" | ||
| 46 | #include "jemalloc/internal/prng.h" | ||
| 47 | #include "jemalloc/internal/util.h" | ||
| 48 | |||
| 49 | /******************************************************************************/ | ||
| 50 | /* Function prototypes for non-inline static functions. */ | ||
| 51 | |||
| 52 | static bool ckh_grow(tsd_t *tsd, ckh_t *ckh); | ||
| 53 | static void ckh_shrink(tsd_t *tsd, ckh_t *ckh); | ||
| 54 | |||
| 55 | /******************************************************************************/ | ||
| 56 | |||
| 57 | /* | ||
| 58 | * Search bucket for key and return the cell number if found; SIZE_T_MAX | ||
| 59 | * otherwise. | ||
| 60 | */ | ||
| 61 | static size_t | ||
| 62 | ckh_bucket_search(ckh_t *ckh, size_t bucket, const void *key) { | ||
| 63 | ckhc_t *cell; | ||
| 64 | unsigned i; | ||
| 65 | |||
| 66 | for (i = 0; i < (ZU(1) << LG_CKH_BUCKET_CELLS); i++) { | ||
| 67 | cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + i]; | ||
| 68 | if (cell->key != NULL && ckh->keycomp(key, cell->key)) { | ||
| 69 | return (bucket << LG_CKH_BUCKET_CELLS) + i; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | return SIZE_T_MAX; | ||
| 74 | } | ||
| 75 | |||
| 76 | /* | ||
| 77 | * Search table for key and return cell number if found; SIZE_T_MAX otherwise. | ||
| 78 | */ | ||
| 79 | static size_t | ||
| 80 | ckh_isearch(ckh_t *ckh, const void *key) { | ||
| 81 | size_t hashes[2], bucket, cell; | ||
| 82 | |||
| 83 | assert(ckh != NULL); | ||
| 84 | |||
| 85 | ckh->hash(key, hashes); | ||
| 86 | |||
| 87 | /* Search primary bucket. */ | ||
| 88 | bucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1); | ||
| 89 | cell = ckh_bucket_search(ckh, bucket, key); | ||
| 90 | if (cell != SIZE_T_MAX) { | ||
| 91 | return cell; | ||
| 92 | } | ||
| 93 | |||
| 94 | /* Search secondary bucket. */ | ||
| 95 | bucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1); | ||
| 96 | cell = ckh_bucket_search(ckh, bucket, key); | ||
| 97 | return cell; | ||
| 98 | } | ||
| 99 | |||
| 100 | static bool | ||
| 101 | ckh_try_bucket_insert(ckh_t *ckh, size_t bucket, const void *key, | ||
| 102 | const void *data) { | ||
| 103 | ckhc_t *cell; | ||
| 104 | unsigned offset, i; | ||
| 105 | |||
| 106 | /* | ||
| 107 | * Cycle through the cells in the bucket, starting at a random position. | ||
| 108 | * The randomness avoids worst-case search overhead as buckets fill up. | ||
| 109 | */ | ||
| 110 | offset = (unsigned)prng_lg_range_u64(&ckh->prng_state, | ||
| 111 | LG_CKH_BUCKET_CELLS); | ||
| 112 | for (i = 0; i < (ZU(1) << LG_CKH_BUCKET_CELLS); i++) { | ||
| 113 | cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + | ||
| 114 | ((i + offset) & ((ZU(1) << LG_CKH_BUCKET_CELLS) - 1))]; | ||
| 115 | if (cell->key == NULL) { | ||
| 116 | cell->key = key; | ||
| 117 | cell->data = data; | ||
| 118 | ckh->count++; | ||
| 119 | return false; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | return true; | ||
| 124 | } | ||
| 125 | |||
| 126 | /* | ||
| 127 | * No space is available in bucket. Randomly evict an item, then try to find an | ||
| 128 | * alternate location for that item. Iteratively repeat this | ||
| 129 | * eviction/relocation procedure until either success or detection of an | ||
| 130 | * eviction/relocation bucket cycle. | ||
| 131 | */ | ||
| 132 | static bool | ||
| 133 | ckh_evict_reloc_insert(ckh_t *ckh, size_t argbucket, void const **argkey, | ||
| 134 | void const **argdata) { | ||
| 135 | const void *key, *data, *tkey, *tdata; | ||
| 136 | ckhc_t *cell; | ||
| 137 | size_t hashes[2], bucket, tbucket; | ||
| 138 | unsigned i; | ||
| 139 | |||
| 140 | bucket = argbucket; | ||
| 141 | key = *argkey; | ||
| 142 | data = *argdata; | ||
| 143 | while (true) { | ||
| 144 | /* | ||
| 145 | * Choose a random item within the bucket to evict. This is | ||
| 146 | * critical to correct function, because without (eventually) | ||
| 147 | * evicting all items within a bucket during iteration, it | ||
| 148 | * would be possible to get stuck in an infinite loop if there | ||
| 149 | * were an item for which both hashes indicated the same | ||
| 150 | * bucket. | ||
| 151 | */ | ||
| 152 | i = (unsigned)prng_lg_range_u64(&ckh->prng_state, | ||
| 153 | LG_CKH_BUCKET_CELLS); | ||
| 154 | cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + i]; | ||
| 155 | assert(cell->key != NULL); | ||
| 156 | |||
| 157 | /* Swap cell->{key,data} and {key,data} (evict). */ | ||
| 158 | tkey = cell->key; tdata = cell->data; | ||
| 159 | cell->key = key; cell->data = data; | ||
| 160 | key = tkey; data = tdata; | ||
| 161 | |||
| 162 | #ifdef CKH_COUNT | ||
| 163 | ckh->nrelocs++; | ||
| 164 | #endif | ||
| 165 | |||
| 166 | /* Find the alternate bucket for the evicted item. */ | ||
| 167 | ckh->hash(key, hashes); | ||
| 168 | tbucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1); | ||
| 169 | if (tbucket == bucket) { | ||
| 170 | tbucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) | ||
| 171 | - 1); | ||
| 172 | /* | ||
| 173 | * It may be that (tbucket == bucket) still, if the | ||
| 174 | * item's hashes both indicate this bucket. However, | ||
| 175 | * we are guaranteed to eventually escape this bucket | ||
| 176 | * during iteration, assuming pseudo-random item | ||
| 177 | * selection (true randomness would make infinite | ||
| 178 | * looping a remote possibility). The reason we can | ||
| 179 | * never get trapped forever is that there are two | ||
| 180 | * cases: | ||
| 181 | * | ||
| 182 | * 1) This bucket == argbucket, so we will quickly | ||
| 183 | * detect an eviction cycle and terminate. | ||
| 184 | * 2) An item was evicted to this bucket from another, | ||
| 185 | * which means that at least one item in this bucket | ||
| 186 | * has hashes that indicate distinct buckets. | ||
| 187 | */ | ||
| 188 | } | ||
| 189 | /* Check for a cycle. */ | ||
| 190 | if (tbucket == argbucket) { | ||
| 191 | *argkey = key; | ||
| 192 | *argdata = data; | ||
| 193 | return true; | ||
| 194 | } | ||
| 195 | |||
| 196 | bucket = tbucket; | ||
| 197 | if (!ckh_try_bucket_insert(ckh, bucket, key, data)) { | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | static bool | ||
| 204 | ckh_try_insert(ckh_t *ckh, void const**argkey, void const**argdata) { | ||
| 205 | size_t hashes[2], bucket; | ||
| 206 | const void *key = *argkey; | ||
| 207 | const void *data = *argdata; | ||
| 208 | |||
| 209 | ckh->hash(key, hashes); | ||
| 210 | |||
| 211 | /* Try to insert in primary bucket. */ | ||
| 212 | bucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1); | ||
| 213 | if (!ckh_try_bucket_insert(ckh, bucket, key, data)) { | ||
| 214 | return false; | ||
| 215 | } | ||
| 216 | |||
| 217 | /* Try to insert in secondary bucket. */ | ||
| 218 | bucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1); | ||
| 219 | if (!ckh_try_bucket_insert(ckh, bucket, key, data)) { | ||
| 220 | return false; | ||
| 221 | } | ||
| 222 | |||
| 223 | /* | ||
| 224 | * Try to find a place for this item via iterative eviction/relocation. | ||
| 225 | */ | ||
| 226 | return ckh_evict_reloc_insert(ckh, bucket, argkey, argdata); | ||
| 227 | } | ||
| 228 | |||
| 229 | /* | ||
| 230 | * Try to rebuild the hash table from scratch by inserting all items from the | ||
| 231 | * old table into the new. | ||
| 232 | */ | ||
| 233 | static bool | ||
| 234 | ckh_rebuild(ckh_t *ckh, ckhc_t *aTab) { | ||
| 235 | size_t count, i, nins; | ||
| 236 | const void *key, *data; | ||
| 237 | |||
| 238 | count = ckh->count; | ||
| 239 | ckh->count = 0; | ||
| 240 | for (i = nins = 0; nins < count; i++) { | ||
| 241 | if (aTab[i].key != NULL) { | ||
| 242 | key = aTab[i].key; | ||
| 243 | data = aTab[i].data; | ||
| 244 | if (ckh_try_insert(ckh, &key, &data)) { | ||
| 245 | ckh->count = count; | ||
| 246 | return true; | ||
| 247 | } | ||
| 248 | nins++; | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | return false; | ||
| 253 | } | ||
| 254 | |||
| 255 | static bool | ||
| 256 | ckh_grow(tsd_t *tsd, ckh_t *ckh) { | ||
| 257 | bool ret; | ||
| 258 | ckhc_t *tab, *ttab; | ||
| 259 | unsigned lg_prevbuckets, lg_curcells; | ||
| 260 | |||
| 261 | #ifdef CKH_COUNT | ||
| 262 | ckh->ngrows++; | ||
| 263 | #endif | ||
| 264 | |||
| 265 | /* | ||
| 266 | * It is possible (though unlikely, given well behaved hashes) that the | ||
| 267 | * table will have to be doubled more than once in order to create a | ||
| 268 | * usable table. | ||
| 269 | */ | ||
| 270 | lg_prevbuckets = ckh->lg_curbuckets; | ||
| 271 | lg_curcells = ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS; | ||
| 272 | while (true) { | ||
| 273 | size_t usize; | ||
| 274 | |||
| 275 | lg_curcells++; | ||
| 276 | usize = sz_sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE); | ||
| 277 | if (unlikely(usize == 0 | ||
| 278 | || usize > SC_LARGE_MAXCLASS)) { | ||
| 279 | ret = true; | ||
| 280 | goto label_return; | ||
| 281 | } | ||
| 282 | tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, | ||
| 283 | true, NULL, true, arena_ichoose(tsd, NULL)); | ||
| 284 | if (tab == NULL) { | ||
| 285 | ret = true; | ||
| 286 | goto label_return; | ||
| 287 | } | ||
| 288 | /* Swap in new table. */ | ||
| 289 | ttab = ckh->tab; | ||
| 290 | ckh->tab = tab; | ||
| 291 | tab = ttab; | ||
| 292 | ckh->lg_curbuckets = lg_curcells - LG_CKH_BUCKET_CELLS; | ||
| 293 | |||
| 294 | if (!ckh_rebuild(ckh, tab)) { | ||
| 295 | idalloctm(tsd_tsdn(tsd), tab, NULL, NULL, true, true); | ||
| 296 | break; | ||
| 297 | } | ||
| 298 | |||
| 299 | /* Rebuilding failed, so back out partially rebuilt table. */ | ||
| 300 | idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true); | ||
| 301 | ckh->tab = tab; | ||
| 302 | ckh->lg_curbuckets = lg_prevbuckets; | ||
| 303 | } | ||
| 304 | |||
| 305 | ret = false; | ||
| 306 | label_return: | ||
| 307 | return ret; | ||
| 308 | } | ||
| 309 | |||
| 310 | static void | ||
| 311 | ckh_shrink(tsd_t *tsd, ckh_t *ckh) { | ||
| 312 | ckhc_t *tab, *ttab; | ||
| 313 | size_t usize; | ||
| 314 | unsigned lg_prevbuckets, lg_curcells; | ||
| 315 | |||
| 316 | /* | ||
| 317 | * It is possible (though unlikely, given well behaved hashes) that the | ||
| 318 | * table rebuild will fail. | ||
| 319 | */ | ||
| 320 | lg_prevbuckets = ckh->lg_curbuckets; | ||
| 321 | lg_curcells = ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS - 1; | ||
| 322 | usize = sz_sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE); | ||
| 323 | if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { | ||
| 324 | return; | ||
| 325 | } | ||
| 326 | tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true, NULL, | ||
| 327 | true, arena_ichoose(tsd, NULL)); | ||
| 328 | if (tab == NULL) { | ||
| 329 | /* | ||
| 330 | * An OOM error isn't worth propagating, since it doesn't | ||
| 331 | * prevent this or future operations from proceeding. | ||
| 332 | */ | ||
| 333 | return; | ||
| 334 | } | ||
| 335 | /* Swap in new table. */ | ||
| 336 | ttab = ckh->tab; | ||
| 337 | ckh->tab = tab; | ||
| 338 | tab = ttab; | ||
| 339 | ckh->lg_curbuckets = lg_curcells - LG_CKH_BUCKET_CELLS; | ||
| 340 | |||
| 341 | if (!ckh_rebuild(ckh, tab)) { | ||
| 342 | idalloctm(tsd_tsdn(tsd), tab, NULL, NULL, true, true); | ||
| 343 | #ifdef CKH_COUNT | ||
| 344 | ckh->nshrinks++; | ||
| 345 | #endif | ||
| 346 | return; | ||
| 347 | } | ||
| 348 | |||
| 349 | /* Rebuilding failed, so back out partially rebuilt table. */ | ||
| 350 | idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true); | ||
| 351 | ckh->tab = tab; | ||
| 352 | ckh->lg_curbuckets = lg_prevbuckets; | ||
| 353 | #ifdef CKH_COUNT | ||
| 354 | ckh->nshrinkfails++; | ||
| 355 | #endif | ||
| 356 | } | ||
| 357 | |||
| 358 | bool | ||
| 359 | ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *ckh_hash, | ||
| 360 | ckh_keycomp_t *keycomp) { | ||
| 361 | bool ret; | ||
| 362 | size_t mincells, usize; | ||
| 363 | unsigned lg_mincells; | ||
| 364 | |||
| 365 | assert(minitems > 0); | ||
| 366 | assert(ckh_hash != NULL); | ||
| 367 | assert(keycomp != NULL); | ||
| 368 | |||
| 369 | #ifdef CKH_COUNT | ||
| 370 | ckh->ngrows = 0; | ||
| 371 | ckh->nshrinks = 0; | ||
| 372 | ckh->nshrinkfails = 0; | ||
| 373 | ckh->ninserts = 0; | ||
| 374 | ckh->nrelocs = 0; | ||
| 375 | #endif | ||
| 376 | ckh->prng_state = 42; /* Value doesn't really matter. */ | ||
| 377 | ckh->count = 0; | ||
| 378 | |||
| 379 | /* | ||
| 380 | * Find the minimum power of 2 that is large enough to fit minitems | ||
| 381 | * entries. We are using (2+,2) cuckoo hashing, which has an expected | ||
| 382 | * maximum load factor of at least ~0.86, so 0.75 is a conservative load | ||
| 383 | * factor that will typically allow mincells items to fit without ever | ||
| 384 | * growing the table. | ||
| 385 | */ | ||
| 386 | assert(LG_CKH_BUCKET_CELLS > 0); | ||
| 387 | mincells = ((minitems + (3 - (minitems % 3))) / 3) << 2; | ||
| 388 | for (lg_mincells = LG_CKH_BUCKET_CELLS; | ||
| 389 | (ZU(1) << lg_mincells) < mincells; | ||
| 390 | lg_mincells++) { | ||
| 391 | /* Do nothing. */ | ||
| 392 | } | ||
| 393 | ckh->lg_minbuckets = lg_mincells - LG_CKH_BUCKET_CELLS; | ||
| 394 | ckh->lg_curbuckets = lg_mincells - LG_CKH_BUCKET_CELLS; | ||
| 395 | ckh->hash = ckh_hash; | ||
| 396 | ckh->keycomp = keycomp; | ||
| 397 | |||
| 398 | usize = sz_sa2u(sizeof(ckhc_t) << lg_mincells, CACHELINE); | ||
| 399 | if (unlikely(usize == 0 || usize > SC_LARGE_MAXCLASS)) { | ||
| 400 | ret = true; | ||
| 401 | goto label_return; | ||
| 402 | } | ||
| 403 | ckh->tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true, | ||
| 404 | NULL, true, arena_ichoose(tsd, NULL)); | ||
| 405 | if (ckh->tab == NULL) { | ||
| 406 | ret = true; | ||
| 407 | goto label_return; | ||
| 408 | } | ||
| 409 | |||
| 410 | ret = false; | ||
| 411 | label_return: | ||
| 412 | return ret; | ||
| 413 | } | ||
| 414 | |||
| 415 | void | ||
| 416 | ckh_delete(tsd_t *tsd, ckh_t *ckh) { | ||
| 417 | assert(ckh != NULL); | ||
| 418 | |||
| 419 | #ifdef CKH_VERBOSE | ||
| 420 | malloc_printf( | ||
| 421 | "%s(%p): ngrows: %"FMTu64", nshrinks: %"FMTu64"," | ||
| 422 | " nshrinkfails: %"FMTu64", ninserts: %"FMTu64"," | ||
| 423 | " nrelocs: %"FMTu64"\n", __func__, ckh, | ||
| 424 | (unsigned long long)ckh->ngrows, | ||
| 425 | (unsigned long long)ckh->nshrinks, | ||
| 426 | (unsigned long long)ckh->nshrinkfails, | ||
| 427 | (unsigned long long)ckh->ninserts, | ||
| 428 | (unsigned long long)ckh->nrelocs); | ||
| 429 | #endif | ||
| 430 | |||
| 431 | idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true); | ||
| 432 | if (config_debug) { | ||
| 433 | memset(ckh, JEMALLOC_FREE_JUNK, sizeof(ckh_t)); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | size_t | ||
| 438 | ckh_count(ckh_t *ckh) { | ||
| 439 | assert(ckh != NULL); | ||
| 440 | |||
| 441 | return ckh->count; | ||
| 442 | } | ||
| 443 | |||
| 444 | bool | ||
| 445 | ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data) { | ||
| 446 | size_t i, ncells; | ||
| 447 | |||
| 448 | for (i = *tabind, ncells = (ZU(1) << (ckh->lg_curbuckets + | ||
| 449 | LG_CKH_BUCKET_CELLS)); i < ncells; i++) { | ||
| 450 | if (ckh->tab[i].key != NULL) { | ||
| 451 | if (key != NULL) { | ||
| 452 | *key = (void *)ckh->tab[i].key; | ||
| 453 | } | ||
| 454 | if (data != NULL) { | ||
| 455 | *data = (void *)ckh->tab[i].data; | ||
| 456 | } | ||
| 457 | *tabind = i + 1; | ||
| 458 | return false; | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | return true; | ||
| 463 | } | ||
| 464 | |||
| 465 | bool | ||
| 466 | ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data) { | ||
| 467 | bool ret; | ||
| 468 | |||
| 469 | assert(ckh != NULL); | ||
| 470 | assert(ckh_search(ckh, key, NULL, NULL)); | ||
| 471 | |||
| 472 | #ifdef CKH_COUNT | ||
| 473 | ckh->ninserts++; | ||
| 474 | #endif | ||
| 475 | |||
| 476 | while (ckh_try_insert(ckh, &key, &data)) { | ||
| 477 | if (ckh_grow(tsd, ckh)) { | ||
| 478 | ret = true; | ||
| 479 | goto label_return; | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | ret = false; | ||
| 484 | label_return: | ||
| 485 | return ret; | ||
| 486 | } | ||
| 487 | |||
| 488 | bool | ||
| 489 | ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key, | ||
| 490 | void **data) { | ||
| 491 | size_t cell; | ||
| 492 | |||
| 493 | assert(ckh != NULL); | ||
| 494 | |||
| 495 | cell = ckh_isearch(ckh, searchkey); | ||
| 496 | if (cell != SIZE_T_MAX) { | ||
| 497 | if (key != NULL) { | ||
| 498 | *key = (void *)ckh->tab[cell].key; | ||
| 499 | } | ||
| 500 | if (data != NULL) { | ||
| 501 | *data = (void *)ckh->tab[cell].data; | ||
| 502 | } | ||
| 503 | ckh->tab[cell].key = NULL; | ||
| 504 | ckh->tab[cell].data = NULL; /* Not necessary. */ | ||
| 505 | |||
| 506 | ckh->count--; | ||
| 507 | /* Try to halve the table if it is less than 1/4 full. */ | ||
| 508 | if (ckh->count < (ZU(1) << (ckh->lg_curbuckets | ||
| 509 | + LG_CKH_BUCKET_CELLS - 2)) && ckh->lg_curbuckets | ||
| 510 | > ckh->lg_minbuckets) { | ||
| 511 | /* Ignore error due to OOM. */ | ||
| 512 | ckh_shrink(tsd, ckh); | ||
| 513 | } | ||
| 514 | |||
| 515 | return false; | ||
| 516 | } | ||
| 517 | |||
| 518 | return true; | ||
| 519 | } | ||
| 520 | |||
| 521 | bool | ||
| 522 | ckh_search(ckh_t *ckh, const void *searchkey, void **key, void **data) { | ||
| 523 | size_t cell; | ||
| 524 | |||
| 525 | assert(ckh != NULL); | ||
| 526 | |||
| 527 | cell = ckh_isearch(ckh, searchkey); | ||
| 528 | if (cell != SIZE_T_MAX) { | ||
| 529 | if (key != NULL) { | ||
| 530 | *key = (void *)ckh->tab[cell].key; | ||
| 531 | } | ||
| 532 | if (data != NULL) { | ||
| 533 | *data = (void *)ckh->tab[cell].data; | ||
| 534 | } | ||
| 535 | return false; | ||
| 536 | } | ||
| 537 | |||
| 538 | return true; | ||
| 539 | } | ||
| 540 | |||
| 541 | void | ||
| 542 | ckh_string_hash(const void *key, size_t r_hash[2]) { | ||
| 543 | hash(key, strlen((const char *)key), 0x94122f33U, r_hash); | ||
| 544 | } | ||
| 545 | |||
| 546 | bool | ||
| 547 | ckh_string_keycomp(const void *k1, const void *k2) { | ||
| 548 | assert(k1 != NULL); | ||
| 549 | assert(k2 != NULL); | ||
| 550 | |||
| 551 | return !strcmp((char *)k1, (char *)k2); | ||
| 552 | } | ||
| 553 | |||
| 554 | void | ||
| 555 | ckh_pointer_hash(const void *key, size_t r_hash[2]) { | ||
| 556 | union { | ||
| 557 | const void *v; | ||
| 558 | size_t i; | ||
| 559 | } u; | ||
| 560 | |||
| 561 | assert(sizeof(u.v) == sizeof(u.i)); | ||
| 562 | u.v = key; | ||
| 563 | hash(&u.i, sizeof(u.i), 0xd983396eU, r_hash); | ||
| 564 | } | ||
| 565 | |||
| 566 | bool | ||
| 567 | ckh_pointer_keycomp(const void *k1, const void *k2) { | ||
| 568 | return (k1 == k2); | ||
| 569 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/counter.c b/examples/redis-unstable/deps/jemalloc/src/counter.c deleted file mode 100644 index 8f1ae3a..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/counter.c +++ /dev/null | |||
| @@ -1,30 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/counter.h" | ||
| 5 | |||
| 6 | bool | ||
| 7 | counter_accum_init(counter_accum_t *counter, uint64_t interval) { | ||
| 8 | if (LOCKEDINT_MTX_INIT(counter->mtx, "counter_accum", | ||
| 9 | WITNESS_RANK_COUNTER_ACCUM, malloc_mutex_rank_exclusive)) { | ||
| 10 | return true; | ||
| 11 | } | ||
| 12 | locked_init_u64_unsynchronized(&counter->accumbytes, 0); | ||
| 13 | counter->interval = interval; | ||
| 14 | return false; | ||
| 15 | } | ||
| 16 | |||
| 17 | void | ||
| 18 | counter_prefork(tsdn_t *tsdn, counter_accum_t *counter) { | ||
| 19 | LOCKEDINT_MTX_PREFORK(tsdn, counter->mtx); | ||
| 20 | } | ||
| 21 | |||
| 22 | void | ||
| 23 | counter_postfork_parent(tsdn_t *tsdn, counter_accum_t *counter) { | ||
| 24 | LOCKEDINT_MTX_POSTFORK_PARENT(tsdn, counter->mtx); | ||
| 25 | } | ||
| 26 | |||
| 27 | void | ||
| 28 | counter_postfork_child(tsdn_t *tsdn, counter_accum_t *counter) { | ||
| 29 | LOCKEDINT_MTX_POSTFORK_CHILD(tsdn, counter->mtx); | ||
| 30 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/ctl.c b/examples/redis-unstable/deps/jemalloc/src/ctl.c deleted file mode 100644 index 135271b..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/ctl.c +++ /dev/null | |||
| @@ -1,4414 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/ctl.h" | ||
| 6 | #include "jemalloc/internal/extent_dss.h" | ||
| 7 | #include "jemalloc/internal/extent_mmap.h" | ||
| 8 | #include "jemalloc/internal/inspect.h" | ||
| 9 | #include "jemalloc/internal/mutex.h" | ||
| 10 | #include "jemalloc/internal/nstime.h" | ||
| 11 | #include "jemalloc/internal/peak_event.h" | ||
| 12 | #include "jemalloc/internal/prof_data.h" | ||
| 13 | #include "jemalloc/internal/prof_log.h" | ||
| 14 | #include "jemalloc/internal/prof_recent.h" | ||
| 15 | #include "jemalloc/internal/prof_stats.h" | ||
| 16 | #include "jemalloc/internal/prof_sys.h" | ||
| 17 | #include "jemalloc/internal/safety_check.h" | ||
| 18 | #include "jemalloc/internal/sc.h" | ||
| 19 | #include "jemalloc/internal/util.h" | ||
| 20 | |||
| 21 | /******************************************************************************/ | ||
| 22 | /* Data. */ | ||
| 23 | |||
| 24 | /* | ||
| 25 | * ctl_mtx protects the following: | ||
| 26 | * - ctl_stats->* | ||
| 27 | */ | ||
| 28 | static malloc_mutex_t ctl_mtx; | ||
| 29 | static bool ctl_initialized; | ||
| 30 | static ctl_stats_t *ctl_stats; | ||
| 31 | static ctl_arenas_t *ctl_arenas; | ||
| 32 | |||
| 33 | /******************************************************************************/ | ||
| 34 | /* Helpers for named and indexed nodes. */ | ||
| 35 | |||
| 36 | static const ctl_named_node_t * | ||
| 37 | ctl_named_node(const ctl_node_t *node) { | ||
| 38 | return ((node->named) ? (const ctl_named_node_t *)node : NULL); | ||
| 39 | } | ||
| 40 | |||
| 41 | static const ctl_named_node_t * | ||
| 42 | ctl_named_children(const ctl_named_node_t *node, size_t index) { | ||
| 43 | const ctl_named_node_t *children = ctl_named_node(node->children); | ||
| 44 | |||
| 45 | return (children ? &children[index] : NULL); | ||
| 46 | } | ||
| 47 | |||
| 48 | static const ctl_indexed_node_t * | ||
| 49 | ctl_indexed_node(const ctl_node_t *node) { | ||
| 50 | return (!node->named ? (const ctl_indexed_node_t *)node : NULL); | ||
| 51 | } | ||
| 52 | |||
| 53 | /******************************************************************************/ | ||
| 54 | /* Function prototypes for non-inline static functions. */ | ||
| 55 | |||
| 56 | #define CTL_PROTO(n) \ | ||
| 57 | static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ | ||
| 58 | void *oldp, size_t *oldlenp, void *newp, size_t newlen); | ||
| 59 | |||
| 60 | #define INDEX_PROTO(n) \ | ||
| 61 | static const ctl_named_node_t *n##_index(tsdn_t *tsdn, \ | ||
| 62 | const size_t *mib, size_t miblen, size_t i); | ||
| 63 | |||
| 64 | CTL_PROTO(version) | ||
| 65 | CTL_PROTO(epoch) | ||
| 66 | CTL_PROTO(background_thread) | ||
| 67 | CTL_PROTO(max_background_threads) | ||
| 68 | CTL_PROTO(thread_tcache_enabled) | ||
| 69 | CTL_PROTO(thread_tcache_flush) | ||
| 70 | CTL_PROTO(thread_peak_read) | ||
| 71 | CTL_PROTO(thread_peak_reset) | ||
| 72 | CTL_PROTO(thread_prof_name) | ||
| 73 | CTL_PROTO(thread_prof_active) | ||
| 74 | CTL_PROTO(thread_arena) | ||
| 75 | CTL_PROTO(thread_allocated) | ||
| 76 | CTL_PROTO(thread_allocatedp) | ||
| 77 | CTL_PROTO(thread_deallocated) | ||
| 78 | CTL_PROTO(thread_deallocatedp) | ||
| 79 | CTL_PROTO(thread_idle) | ||
| 80 | CTL_PROTO(config_cache_oblivious) | ||
| 81 | CTL_PROTO(config_debug) | ||
| 82 | CTL_PROTO(config_fill) | ||
| 83 | CTL_PROTO(config_lazy_lock) | ||
| 84 | CTL_PROTO(config_malloc_conf) | ||
| 85 | CTL_PROTO(config_opt_safety_checks) | ||
| 86 | CTL_PROTO(config_prof) | ||
| 87 | CTL_PROTO(config_prof_libgcc) | ||
| 88 | CTL_PROTO(config_prof_libunwind) | ||
| 89 | CTL_PROTO(config_stats) | ||
| 90 | CTL_PROTO(config_utrace) | ||
| 91 | CTL_PROTO(config_xmalloc) | ||
| 92 | CTL_PROTO(opt_abort) | ||
| 93 | CTL_PROTO(opt_abort_conf) | ||
| 94 | CTL_PROTO(opt_cache_oblivious) | ||
| 95 | CTL_PROTO(opt_trust_madvise) | ||
| 96 | CTL_PROTO(opt_confirm_conf) | ||
| 97 | CTL_PROTO(opt_hpa) | ||
| 98 | CTL_PROTO(opt_hpa_slab_max_alloc) | ||
| 99 | CTL_PROTO(opt_hpa_hugification_threshold) | ||
| 100 | CTL_PROTO(opt_hpa_hugify_delay_ms) | ||
| 101 | CTL_PROTO(opt_hpa_min_purge_interval_ms) | ||
| 102 | CTL_PROTO(opt_hpa_dirty_mult) | ||
| 103 | CTL_PROTO(opt_hpa_sec_nshards) | ||
| 104 | CTL_PROTO(opt_hpa_sec_max_alloc) | ||
| 105 | CTL_PROTO(opt_hpa_sec_max_bytes) | ||
| 106 | CTL_PROTO(opt_hpa_sec_bytes_after_flush) | ||
| 107 | CTL_PROTO(opt_hpa_sec_batch_fill_extra) | ||
| 108 | CTL_PROTO(opt_metadata_thp) | ||
| 109 | CTL_PROTO(opt_retain) | ||
| 110 | CTL_PROTO(opt_dss) | ||
| 111 | CTL_PROTO(opt_narenas) | ||
| 112 | CTL_PROTO(opt_percpu_arena) | ||
| 113 | CTL_PROTO(opt_oversize_threshold) | ||
| 114 | CTL_PROTO(opt_background_thread) | ||
| 115 | CTL_PROTO(opt_mutex_max_spin) | ||
| 116 | CTL_PROTO(opt_max_background_threads) | ||
| 117 | CTL_PROTO(opt_dirty_decay_ms) | ||
| 118 | CTL_PROTO(opt_muzzy_decay_ms) | ||
| 119 | CTL_PROTO(opt_stats_print) | ||
| 120 | CTL_PROTO(opt_stats_print_opts) | ||
| 121 | CTL_PROTO(opt_stats_interval) | ||
| 122 | CTL_PROTO(opt_stats_interval_opts) | ||
| 123 | CTL_PROTO(opt_junk) | ||
| 124 | CTL_PROTO(opt_zero) | ||
| 125 | CTL_PROTO(opt_utrace) | ||
| 126 | CTL_PROTO(opt_xmalloc) | ||
| 127 | CTL_PROTO(opt_experimental_infallible_new) | ||
| 128 | CTL_PROTO(opt_tcache) | ||
| 129 | CTL_PROTO(opt_tcache_max) | ||
| 130 | CTL_PROTO(opt_tcache_nslots_small_min) | ||
| 131 | CTL_PROTO(opt_tcache_nslots_small_max) | ||
| 132 | CTL_PROTO(opt_tcache_nslots_large) | ||
| 133 | CTL_PROTO(opt_lg_tcache_nslots_mul) | ||
| 134 | CTL_PROTO(opt_tcache_gc_incr_bytes) | ||
| 135 | CTL_PROTO(opt_tcache_gc_delay_bytes) | ||
| 136 | CTL_PROTO(opt_lg_tcache_flush_small_div) | ||
| 137 | CTL_PROTO(opt_lg_tcache_flush_large_div) | ||
| 138 | CTL_PROTO(opt_thp) | ||
| 139 | CTL_PROTO(opt_lg_extent_max_active_fit) | ||
| 140 | CTL_PROTO(opt_prof) | ||
| 141 | CTL_PROTO(opt_prof_prefix) | ||
| 142 | CTL_PROTO(opt_prof_active) | ||
| 143 | CTL_PROTO(opt_prof_thread_active_init) | ||
| 144 | CTL_PROTO(opt_lg_prof_sample) | ||
| 145 | CTL_PROTO(opt_lg_prof_interval) | ||
| 146 | CTL_PROTO(opt_prof_gdump) | ||
| 147 | CTL_PROTO(opt_prof_final) | ||
| 148 | CTL_PROTO(opt_prof_leak) | ||
| 149 | CTL_PROTO(opt_prof_leak_error) | ||
| 150 | CTL_PROTO(opt_prof_accum) | ||
| 151 | CTL_PROTO(opt_prof_recent_alloc_max) | ||
| 152 | CTL_PROTO(opt_prof_stats) | ||
| 153 | CTL_PROTO(opt_prof_sys_thread_name) | ||
| 154 | CTL_PROTO(opt_prof_time_res) | ||
| 155 | CTL_PROTO(opt_lg_san_uaf_align) | ||
| 156 | CTL_PROTO(opt_zero_realloc) | ||
| 157 | CTL_PROTO(tcache_create) | ||
| 158 | CTL_PROTO(tcache_flush) | ||
| 159 | CTL_PROTO(tcache_destroy) | ||
| 160 | CTL_PROTO(arena_i_initialized) | ||
| 161 | CTL_PROTO(arena_i_decay) | ||
| 162 | CTL_PROTO(arena_i_purge) | ||
| 163 | CTL_PROTO(arena_i_reset) | ||
| 164 | CTL_PROTO(arena_i_destroy) | ||
| 165 | CTL_PROTO(arena_i_dss) | ||
| 166 | CTL_PROTO(arena_i_oversize_threshold) | ||
| 167 | CTL_PROTO(arena_i_dirty_decay_ms) | ||
| 168 | CTL_PROTO(arena_i_muzzy_decay_ms) | ||
| 169 | CTL_PROTO(arena_i_extent_hooks) | ||
| 170 | CTL_PROTO(arena_i_retain_grow_limit) | ||
| 171 | INDEX_PROTO(arena_i) | ||
| 172 | CTL_PROTO(arenas_bin_i_size) | ||
| 173 | CTL_PROTO(arenas_bin_i_nregs) | ||
| 174 | CTL_PROTO(arenas_bin_i_slab_size) | ||
| 175 | CTL_PROTO(arenas_bin_i_nshards) | ||
| 176 | INDEX_PROTO(arenas_bin_i) | ||
| 177 | CTL_PROTO(arenas_lextent_i_size) | ||
| 178 | INDEX_PROTO(arenas_lextent_i) | ||
| 179 | CTL_PROTO(arenas_narenas) | ||
| 180 | CTL_PROTO(arenas_dirty_decay_ms) | ||
| 181 | CTL_PROTO(arenas_muzzy_decay_ms) | ||
| 182 | CTL_PROTO(arenas_quantum) | ||
| 183 | CTL_PROTO(arenas_page) | ||
| 184 | CTL_PROTO(arenas_tcache_max) | ||
| 185 | CTL_PROTO(arenas_nbins) | ||
| 186 | CTL_PROTO(arenas_nhbins) | ||
| 187 | CTL_PROTO(arenas_nlextents) | ||
| 188 | CTL_PROTO(arenas_create) | ||
| 189 | CTL_PROTO(arenas_lookup) | ||
| 190 | CTL_PROTO(prof_thread_active_init) | ||
| 191 | CTL_PROTO(prof_active) | ||
| 192 | CTL_PROTO(prof_dump) | ||
| 193 | CTL_PROTO(prof_gdump) | ||
| 194 | CTL_PROTO(prof_prefix) | ||
| 195 | CTL_PROTO(prof_reset) | ||
| 196 | CTL_PROTO(prof_interval) | ||
| 197 | CTL_PROTO(lg_prof_sample) | ||
| 198 | CTL_PROTO(prof_log_start) | ||
| 199 | CTL_PROTO(prof_log_stop) | ||
| 200 | CTL_PROTO(prof_stats_bins_i_live) | ||
| 201 | CTL_PROTO(prof_stats_bins_i_accum) | ||
| 202 | INDEX_PROTO(prof_stats_bins_i) | ||
| 203 | CTL_PROTO(prof_stats_lextents_i_live) | ||
| 204 | CTL_PROTO(prof_stats_lextents_i_accum) | ||
| 205 | INDEX_PROTO(prof_stats_lextents_i) | ||
| 206 | CTL_PROTO(stats_arenas_i_small_allocated) | ||
| 207 | CTL_PROTO(stats_arenas_i_small_nmalloc) | ||
| 208 | CTL_PROTO(stats_arenas_i_small_ndalloc) | ||
| 209 | CTL_PROTO(stats_arenas_i_small_nrequests) | ||
| 210 | CTL_PROTO(stats_arenas_i_small_nfills) | ||
| 211 | CTL_PROTO(stats_arenas_i_small_nflushes) | ||
| 212 | CTL_PROTO(stats_arenas_i_large_allocated) | ||
| 213 | CTL_PROTO(stats_arenas_i_large_nmalloc) | ||
| 214 | CTL_PROTO(stats_arenas_i_large_ndalloc) | ||
| 215 | CTL_PROTO(stats_arenas_i_large_nrequests) | ||
| 216 | CTL_PROTO(stats_arenas_i_large_nfills) | ||
| 217 | CTL_PROTO(stats_arenas_i_large_nflushes) | ||
| 218 | CTL_PROTO(stats_arenas_i_bins_j_nmalloc) | ||
| 219 | CTL_PROTO(stats_arenas_i_bins_j_ndalloc) | ||
| 220 | CTL_PROTO(stats_arenas_i_bins_j_nrequests) | ||
| 221 | CTL_PROTO(stats_arenas_i_bins_j_curregs) | ||
| 222 | CTL_PROTO(stats_arenas_i_bins_j_nfills) | ||
| 223 | CTL_PROTO(stats_arenas_i_bins_j_nflushes) | ||
| 224 | CTL_PROTO(stats_arenas_i_bins_j_nslabs) | ||
| 225 | CTL_PROTO(stats_arenas_i_bins_j_nreslabs) | ||
| 226 | CTL_PROTO(stats_arenas_i_bins_j_curslabs) | ||
| 227 | CTL_PROTO(stats_arenas_i_bins_j_nonfull_slabs) | ||
| 228 | INDEX_PROTO(stats_arenas_i_bins_j) | ||
| 229 | CTL_PROTO(stats_arenas_i_lextents_j_nmalloc) | ||
| 230 | CTL_PROTO(stats_arenas_i_lextents_j_ndalloc) | ||
| 231 | CTL_PROTO(stats_arenas_i_lextents_j_nrequests) | ||
| 232 | CTL_PROTO(stats_arenas_i_lextents_j_curlextents) | ||
| 233 | INDEX_PROTO(stats_arenas_i_lextents_j) | ||
| 234 | CTL_PROTO(stats_arenas_i_extents_j_ndirty) | ||
| 235 | CTL_PROTO(stats_arenas_i_extents_j_nmuzzy) | ||
| 236 | CTL_PROTO(stats_arenas_i_extents_j_nretained) | ||
| 237 | CTL_PROTO(stats_arenas_i_extents_j_dirty_bytes) | ||
| 238 | CTL_PROTO(stats_arenas_i_extents_j_muzzy_bytes) | ||
| 239 | CTL_PROTO(stats_arenas_i_extents_j_retained_bytes) | ||
| 240 | INDEX_PROTO(stats_arenas_i_extents_j) | ||
| 241 | CTL_PROTO(stats_arenas_i_hpa_shard_npurge_passes) | ||
| 242 | CTL_PROTO(stats_arenas_i_hpa_shard_npurges) | ||
| 243 | CTL_PROTO(stats_arenas_i_hpa_shard_nhugifies) | ||
| 244 | CTL_PROTO(stats_arenas_i_hpa_shard_ndehugifies) | ||
| 245 | |||
| 246 | /* We have a set of stats for full slabs. */ | ||
| 247 | CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_npageslabs_nonhuge) | ||
| 248 | CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_npageslabs_huge) | ||
| 249 | CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_nactive_nonhuge) | ||
| 250 | CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_nactive_huge) | ||
| 251 | CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_ndirty_nonhuge) | ||
| 252 | CTL_PROTO(stats_arenas_i_hpa_shard_full_slabs_ndirty_huge) | ||
| 253 | |||
| 254 | /* A parallel set for the empty slabs. */ | ||
| 255 | CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_nonhuge) | ||
| 256 | CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_huge) | ||
| 257 | CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_nactive_nonhuge) | ||
| 258 | CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_nactive_huge) | ||
| 259 | CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_ndirty_nonhuge) | ||
| 260 | CTL_PROTO(stats_arenas_i_hpa_shard_empty_slabs_ndirty_huge) | ||
| 261 | |||
| 262 | /* | ||
| 263 | * And one for the slabs that are neither empty nor full, but indexed by how | ||
| 264 | * full they are. | ||
| 265 | */ | ||
| 266 | CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_nonhuge) | ||
| 267 | CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_huge) | ||
| 268 | CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_nonhuge) | ||
| 269 | CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_huge) | ||
| 270 | CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_nonhuge) | ||
| 271 | CTL_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_huge) | ||
| 272 | |||
| 273 | INDEX_PROTO(stats_arenas_i_hpa_shard_nonfull_slabs_j) | ||
| 274 | CTL_PROTO(stats_arenas_i_nthreads) | ||
| 275 | CTL_PROTO(stats_arenas_i_uptime) | ||
| 276 | CTL_PROTO(stats_arenas_i_dss) | ||
| 277 | CTL_PROTO(stats_arenas_i_dirty_decay_ms) | ||
| 278 | CTL_PROTO(stats_arenas_i_muzzy_decay_ms) | ||
| 279 | CTL_PROTO(stats_arenas_i_pactive) | ||
| 280 | CTL_PROTO(stats_arenas_i_pdirty) | ||
| 281 | CTL_PROTO(stats_arenas_i_pmuzzy) | ||
| 282 | CTL_PROTO(stats_arenas_i_mapped) | ||
| 283 | CTL_PROTO(stats_arenas_i_retained) | ||
| 284 | CTL_PROTO(stats_arenas_i_extent_avail) | ||
| 285 | CTL_PROTO(stats_arenas_i_dirty_npurge) | ||
| 286 | CTL_PROTO(stats_arenas_i_dirty_nmadvise) | ||
| 287 | CTL_PROTO(stats_arenas_i_dirty_purged) | ||
| 288 | CTL_PROTO(stats_arenas_i_muzzy_npurge) | ||
| 289 | CTL_PROTO(stats_arenas_i_muzzy_nmadvise) | ||
| 290 | CTL_PROTO(stats_arenas_i_muzzy_purged) | ||
| 291 | CTL_PROTO(stats_arenas_i_base) | ||
| 292 | CTL_PROTO(stats_arenas_i_internal) | ||
| 293 | CTL_PROTO(stats_arenas_i_metadata_thp) | ||
| 294 | CTL_PROTO(stats_arenas_i_tcache_bytes) | ||
| 295 | CTL_PROTO(stats_arenas_i_tcache_stashed_bytes) | ||
| 296 | CTL_PROTO(stats_arenas_i_resident) | ||
| 297 | CTL_PROTO(stats_arenas_i_abandoned_vm) | ||
| 298 | CTL_PROTO(stats_arenas_i_hpa_sec_bytes) | ||
| 299 | INDEX_PROTO(stats_arenas_i) | ||
| 300 | CTL_PROTO(stats_allocated) | ||
| 301 | CTL_PROTO(stats_active) | ||
| 302 | CTL_PROTO(stats_background_thread_num_threads) | ||
| 303 | CTL_PROTO(stats_background_thread_num_runs) | ||
| 304 | CTL_PROTO(stats_background_thread_run_interval) | ||
| 305 | CTL_PROTO(stats_metadata) | ||
| 306 | CTL_PROTO(stats_metadata_thp) | ||
| 307 | CTL_PROTO(stats_resident) | ||
| 308 | CTL_PROTO(stats_mapped) | ||
| 309 | CTL_PROTO(stats_retained) | ||
| 310 | CTL_PROTO(stats_zero_reallocs) | ||
| 311 | CTL_PROTO(experimental_hooks_install) | ||
| 312 | CTL_PROTO(experimental_hooks_remove) | ||
| 313 | CTL_PROTO(experimental_hooks_prof_backtrace) | ||
| 314 | CTL_PROTO(experimental_hooks_prof_dump) | ||
| 315 | CTL_PROTO(experimental_hooks_safety_check_abort) | ||
| 316 | CTL_PROTO(experimental_thread_activity_callback) | ||
| 317 | CTL_PROTO(experimental_utilization_query) | ||
| 318 | CTL_PROTO(experimental_utilization_batch_query) | ||
| 319 | CTL_PROTO(experimental_arenas_i_pactivep) | ||
| 320 | INDEX_PROTO(experimental_arenas_i) | ||
| 321 | CTL_PROTO(experimental_prof_recent_alloc_max) | ||
| 322 | CTL_PROTO(experimental_prof_recent_alloc_dump) | ||
| 323 | CTL_PROTO(experimental_batch_alloc) | ||
| 324 | CTL_PROTO(experimental_arenas_create_ext) | ||
| 325 | |||
| 326 | #define MUTEX_STATS_CTL_PROTO_GEN(n) \ | ||
| 327 | CTL_PROTO(stats_##n##_num_ops) \ | ||
| 328 | CTL_PROTO(stats_##n##_num_wait) \ | ||
| 329 | CTL_PROTO(stats_##n##_num_spin_acq) \ | ||
| 330 | CTL_PROTO(stats_##n##_num_owner_switch) \ | ||
| 331 | CTL_PROTO(stats_##n##_total_wait_time) \ | ||
| 332 | CTL_PROTO(stats_##n##_max_wait_time) \ | ||
| 333 | CTL_PROTO(stats_##n##_max_num_thds) | ||
| 334 | |||
| 335 | /* Global mutexes. */ | ||
| 336 | #define OP(mtx) MUTEX_STATS_CTL_PROTO_GEN(mutexes_##mtx) | ||
| 337 | MUTEX_PROF_GLOBAL_MUTEXES | ||
| 338 | #undef OP | ||
| 339 | |||
| 340 | /* Per arena mutexes. */ | ||
| 341 | #define OP(mtx) MUTEX_STATS_CTL_PROTO_GEN(arenas_i_mutexes_##mtx) | ||
| 342 | MUTEX_PROF_ARENA_MUTEXES | ||
| 343 | #undef OP | ||
| 344 | |||
| 345 | /* Arena bin mutexes. */ | ||
| 346 | MUTEX_STATS_CTL_PROTO_GEN(arenas_i_bins_j_mutex) | ||
| 347 | #undef MUTEX_STATS_CTL_PROTO_GEN | ||
| 348 | |||
| 349 | CTL_PROTO(stats_mutexes_reset) | ||
| 350 | |||
| 351 | /******************************************************************************/ | ||
| 352 | /* mallctl tree. */ | ||
| 353 | |||
| 354 | #define NAME(n) {true}, n | ||
| 355 | #define CHILD(t, c) \ | ||
| 356 | sizeof(c##_node) / sizeof(ctl_##t##_node_t), \ | ||
| 357 | (ctl_node_t *)c##_node, \ | ||
| 358 | NULL | ||
| 359 | #define CTL(c) 0, NULL, c##_ctl | ||
| 360 | |||
| 361 | /* | ||
| 362 | * Only handles internal indexed nodes, since there are currently no external | ||
| 363 | * ones. | ||
| 364 | */ | ||
| 365 | #define INDEX(i) {false}, i##_index | ||
| 366 | |||
| 367 | static const ctl_named_node_t thread_tcache_node[] = { | ||
| 368 | {NAME("enabled"), CTL(thread_tcache_enabled)}, | ||
| 369 | {NAME("flush"), CTL(thread_tcache_flush)} | ||
| 370 | }; | ||
| 371 | |||
| 372 | static const ctl_named_node_t thread_peak_node[] = { | ||
| 373 | {NAME("read"), CTL(thread_peak_read)}, | ||
| 374 | {NAME("reset"), CTL(thread_peak_reset)}, | ||
| 375 | }; | ||
| 376 | |||
| 377 | static const ctl_named_node_t thread_prof_node[] = { | ||
| 378 | {NAME("name"), CTL(thread_prof_name)}, | ||
| 379 | {NAME("active"), CTL(thread_prof_active)} | ||
| 380 | }; | ||
| 381 | |||
| 382 | static const ctl_named_node_t thread_node[] = { | ||
| 383 | {NAME("arena"), CTL(thread_arena)}, | ||
| 384 | {NAME("allocated"), CTL(thread_allocated)}, | ||
| 385 | {NAME("allocatedp"), CTL(thread_allocatedp)}, | ||
| 386 | {NAME("deallocated"), CTL(thread_deallocated)}, | ||
| 387 | {NAME("deallocatedp"), CTL(thread_deallocatedp)}, | ||
| 388 | {NAME("tcache"), CHILD(named, thread_tcache)}, | ||
| 389 | {NAME("peak"), CHILD(named, thread_peak)}, | ||
| 390 | {NAME("prof"), CHILD(named, thread_prof)}, | ||
| 391 | {NAME("idle"), CTL(thread_idle)} | ||
| 392 | }; | ||
| 393 | |||
| 394 | static const ctl_named_node_t config_node[] = { | ||
| 395 | {NAME("cache_oblivious"), CTL(config_cache_oblivious)}, | ||
| 396 | {NAME("debug"), CTL(config_debug)}, | ||
| 397 | {NAME("fill"), CTL(config_fill)}, | ||
| 398 | {NAME("lazy_lock"), CTL(config_lazy_lock)}, | ||
| 399 | {NAME("malloc_conf"), CTL(config_malloc_conf)}, | ||
| 400 | {NAME("opt_safety_checks"), CTL(config_opt_safety_checks)}, | ||
| 401 | {NAME("prof"), CTL(config_prof)}, | ||
| 402 | {NAME("prof_libgcc"), CTL(config_prof_libgcc)}, | ||
| 403 | {NAME("prof_libunwind"), CTL(config_prof_libunwind)}, | ||
| 404 | {NAME("stats"), CTL(config_stats)}, | ||
| 405 | {NAME("utrace"), CTL(config_utrace)}, | ||
| 406 | {NAME("xmalloc"), CTL(config_xmalloc)} | ||
| 407 | }; | ||
| 408 | |||
| 409 | static const ctl_named_node_t opt_node[] = { | ||
| 410 | {NAME("abort"), CTL(opt_abort)}, | ||
| 411 | {NAME("abort_conf"), CTL(opt_abort_conf)}, | ||
| 412 | {NAME("cache_oblivious"), CTL(opt_cache_oblivious)}, | ||
| 413 | {NAME("trust_madvise"), CTL(opt_trust_madvise)}, | ||
| 414 | {NAME("confirm_conf"), CTL(opt_confirm_conf)}, | ||
| 415 | {NAME("hpa"), CTL(opt_hpa)}, | ||
| 416 | {NAME("hpa_slab_max_alloc"), CTL(opt_hpa_slab_max_alloc)}, | ||
| 417 | {NAME("hpa_hugification_threshold"), | ||
| 418 | CTL(opt_hpa_hugification_threshold)}, | ||
| 419 | {NAME("hpa_hugify_delay_ms"), CTL(opt_hpa_hugify_delay_ms)}, | ||
| 420 | {NAME("hpa_min_purge_interval_ms"), CTL(opt_hpa_min_purge_interval_ms)}, | ||
| 421 | {NAME("hpa_dirty_mult"), CTL(opt_hpa_dirty_mult)}, | ||
| 422 | {NAME("hpa_sec_nshards"), CTL(opt_hpa_sec_nshards)}, | ||
| 423 | {NAME("hpa_sec_max_alloc"), CTL(opt_hpa_sec_max_alloc)}, | ||
| 424 | {NAME("hpa_sec_max_bytes"), CTL(opt_hpa_sec_max_bytes)}, | ||
| 425 | {NAME("hpa_sec_bytes_after_flush"), | ||
| 426 | CTL(opt_hpa_sec_bytes_after_flush)}, | ||
| 427 | {NAME("hpa_sec_batch_fill_extra"), | ||
| 428 | CTL(opt_hpa_sec_batch_fill_extra)}, | ||
| 429 | {NAME("metadata_thp"), CTL(opt_metadata_thp)}, | ||
| 430 | {NAME("retain"), CTL(opt_retain)}, | ||
| 431 | {NAME("dss"), CTL(opt_dss)}, | ||
| 432 | {NAME("narenas"), CTL(opt_narenas)}, | ||
| 433 | {NAME("percpu_arena"), CTL(opt_percpu_arena)}, | ||
| 434 | {NAME("oversize_threshold"), CTL(opt_oversize_threshold)}, | ||
| 435 | {NAME("mutex_max_spin"), CTL(opt_mutex_max_spin)}, | ||
| 436 | {NAME("background_thread"), CTL(opt_background_thread)}, | ||
| 437 | {NAME("max_background_threads"), CTL(opt_max_background_threads)}, | ||
| 438 | {NAME("dirty_decay_ms"), CTL(opt_dirty_decay_ms)}, | ||
| 439 | {NAME("muzzy_decay_ms"), CTL(opt_muzzy_decay_ms)}, | ||
| 440 | {NAME("stats_print"), CTL(opt_stats_print)}, | ||
| 441 | {NAME("stats_print_opts"), CTL(opt_stats_print_opts)}, | ||
| 442 | {NAME("stats_interval"), CTL(opt_stats_interval)}, | ||
| 443 | {NAME("stats_interval_opts"), CTL(opt_stats_interval_opts)}, | ||
| 444 | {NAME("junk"), CTL(opt_junk)}, | ||
| 445 | {NAME("zero"), CTL(opt_zero)}, | ||
| 446 | {NAME("utrace"), CTL(opt_utrace)}, | ||
| 447 | {NAME("xmalloc"), CTL(opt_xmalloc)}, | ||
| 448 | {NAME("experimental_infallible_new"), | ||
| 449 | CTL(opt_experimental_infallible_new)}, | ||
| 450 | {NAME("tcache"), CTL(opt_tcache)}, | ||
| 451 | {NAME("tcache_max"), CTL(opt_tcache_max)}, | ||
| 452 | {NAME("tcache_nslots_small_min"), | ||
| 453 | CTL(opt_tcache_nslots_small_min)}, | ||
| 454 | {NAME("tcache_nslots_small_max"), | ||
| 455 | CTL(opt_tcache_nslots_small_max)}, | ||
| 456 | {NAME("tcache_nslots_large"), CTL(opt_tcache_nslots_large)}, | ||
| 457 | {NAME("lg_tcache_nslots_mul"), CTL(opt_lg_tcache_nslots_mul)}, | ||
| 458 | {NAME("tcache_gc_incr_bytes"), CTL(opt_tcache_gc_incr_bytes)}, | ||
| 459 | {NAME("tcache_gc_delay_bytes"), CTL(opt_tcache_gc_delay_bytes)}, | ||
| 460 | {NAME("lg_tcache_flush_small_div"), | ||
| 461 | CTL(opt_lg_tcache_flush_small_div)}, | ||
| 462 | {NAME("lg_tcache_flush_large_div"), | ||
| 463 | CTL(opt_lg_tcache_flush_large_div)}, | ||
| 464 | {NAME("thp"), CTL(opt_thp)}, | ||
| 465 | {NAME("lg_extent_max_active_fit"), CTL(opt_lg_extent_max_active_fit)}, | ||
| 466 | {NAME("prof"), CTL(opt_prof)}, | ||
| 467 | {NAME("prof_prefix"), CTL(opt_prof_prefix)}, | ||
| 468 | {NAME("prof_active"), CTL(opt_prof_active)}, | ||
| 469 | {NAME("prof_thread_active_init"), CTL(opt_prof_thread_active_init)}, | ||
| 470 | {NAME("lg_prof_sample"), CTL(opt_lg_prof_sample)}, | ||
| 471 | {NAME("lg_prof_interval"), CTL(opt_lg_prof_interval)}, | ||
| 472 | {NAME("prof_gdump"), CTL(opt_prof_gdump)}, | ||
| 473 | {NAME("prof_final"), CTL(opt_prof_final)}, | ||
| 474 | {NAME("prof_leak"), CTL(opt_prof_leak)}, | ||
| 475 | {NAME("prof_leak_error"), CTL(opt_prof_leak_error)}, | ||
| 476 | {NAME("prof_accum"), CTL(opt_prof_accum)}, | ||
| 477 | {NAME("prof_recent_alloc_max"), CTL(opt_prof_recent_alloc_max)}, | ||
| 478 | {NAME("prof_stats"), CTL(opt_prof_stats)}, | ||
| 479 | {NAME("prof_sys_thread_name"), CTL(opt_prof_sys_thread_name)}, | ||
| 480 | {NAME("prof_time_resolution"), CTL(opt_prof_time_res)}, | ||
| 481 | {NAME("lg_san_uaf_align"), CTL(opt_lg_san_uaf_align)}, | ||
| 482 | {NAME("zero_realloc"), CTL(opt_zero_realloc)} | ||
| 483 | }; | ||
| 484 | |||
| 485 | static const ctl_named_node_t tcache_node[] = { | ||
| 486 | {NAME("create"), CTL(tcache_create)}, | ||
| 487 | {NAME("flush"), CTL(tcache_flush)}, | ||
| 488 | {NAME("destroy"), CTL(tcache_destroy)} | ||
| 489 | }; | ||
| 490 | |||
| 491 | static const ctl_named_node_t arena_i_node[] = { | ||
| 492 | {NAME("initialized"), CTL(arena_i_initialized)}, | ||
| 493 | {NAME("decay"), CTL(arena_i_decay)}, | ||
| 494 | {NAME("purge"), CTL(arena_i_purge)}, | ||
| 495 | {NAME("reset"), CTL(arena_i_reset)}, | ||
| 496 | {NAME("destroy"), CTL(arena_i_destroy)}, | ||
| 497 | {NAME("dss"), CTL(arena_i_dss)}, | ||
| 498 | /* | ||
| 499 | * Undocumented for now, since we anticipate an arena API in flux after | ||
| 500 | * we cut the last 5-series release. | ||
| 501 | */ | ||
| 502 | {NAME("oversize_threshold"), CTL(arena_i_oversize_threshold)}, | ||
| 503 | {NAME("dirty_decay_ms"), CTL(arena_i_dirty_decay_ms)}, | ||
| 504 | {NAME("muzzy_decay_ms"), CTL(arena_i_muzzy_decay_ms)}, | ||
| 505 | {NAME("extent_hooks"), CTL(arena_i_extent_hooks)}, | ||
| 506 | {NAME("retain_grow_limit"), CTL(arena_i_retain_grow_limit)} | ||
| 507 | }; | ||
| 508 | static const ctl_named_node_t super_arena_i_node[] = { | ||
| 509 | {NAME(""), CHILD(named, arena_i)} | ||
| 510 | }; | ||
| 511 | |||
| 512 | static const ctl_indexed_node_t arena_node[] = { | ||
| 513 | {INDEX(arena_i)} | ||
| 514 | }; | ||
| 515 | |||
| 516 | static const ctl_named_node_t arenas_bin_i_node[] = { | ||
| 517 | {NAME("size"), CTL(arenas_bin_i_size)}, | ||
| 518 | {NAME("nregs"), CTL(arenas_bin_i_nregs)}, | ||
| 519 | {NAME("slab_size"), CTL(arenas_bin_i_slab_size)}, | ||
| 520 | {NAME("nshards"), CTL(arenas_bin_i_nshards)} | ||
| 521 | }; | ||
| 522 | static const ctl_named_node_t super_arenas_bin_i_node[] = { | ||
| 523 | {NAME(""), CHILD(named, arenas_bin_i)} | ||
| 524 | }; | ||
| 525 | |||
| 526 | static const ctl_indexed_node_t arenas_bin_node[] = { | ||
| 527 | {INDEX(arenas_bin_i)} | ||
| 528 | }; | ||
| 529 | |||
| 530 | static const ctl_named_node_t arenas_lextent_i_node[] = { | ||
| 531 | {NAME("size"), CTL(arenas_lextent_i_size)} | ||
| 532 | }; | ||
| 533 | static const ctl_named_node_t super_arenas_lextent_i_node[] = { | ||
| 534 | {NAME(""), CHILD(named, arenas_lextent_i)} | ||
| 535 | }; | ||
| 536 | |||
| 537 | static const ctl_indexed_node_t arenas_lextent_node[] = { | ||
| 538 | {INDEX(arenas_lextent_i)} | ||
| 539 | }; | ||
| 540 | |||
| 541 | static const ctl_named_node_t arenas_node[] = { | ||
| 542 | {NAME("narenas"), CTL(arenas_narenas)}, | ||
| 543 | {NAME("dirty_decay_ms"), CTL(arenas_dirty_decay_ms)}, | ||
| 544 | {NAME("muzzy_decay_ms"), CTL(arenas_muzzy_decay_ms)}, | ||
| 545 | {NAME("quantum"), CTL(arenas_quantum)}, | ||
| 546 | {NAME("page"), CTL(arenas_page)}, | ||
| 547 | {NAME("tcache_max"), CTL(arenas_tcache_max)}, | ||
| 548 | {NAME("nbins"), CTL(arenas_nbins)}, | ||
| 549 | {NAME("nhbins"), CTL(arenas_nhbins)}, | ||
| 550 | {NAME("bin"), CHILD(indexed, arenas_bin)}, | ||
| 551 | {NAME("nlextents"), CTL(arenas_nlextents)}, | ||
| 552 | {NAME("lextent"), CHILD(indexed, arenas_lextent)}, | ||
| 553 | {NAME("create"), CTL(arenas_create)}, | ||
| 554 | {NAME("lookup"), CTL(arenas_lookup)} | ||
| 555 | }; | ||
| 556 | |||
| 557 | static const ctl_named_node_t prof_stats_bins_i_node[] = { | ||
| 558 | {NAME("live"), CTL(prof_stats_bins_i_live)}, | ||
| 559 | {NAME("accum"), CTL(prof_stats_bins_i_accum)} | ||
| 560 | }; | ||
| 561 | |||
| 562 | static const ctl_named_node_t super_prof_stats_bins_i_node[] = { | ||
| 563 | {NAME(""), CHILD(named, prof_stats_bins_i)} | ||
| 564 | }; | ||
| 565 | |||
| 566 | static const ctl_indexed_node_t prof_stats_bins_node[] = { | ||
| 567 | {INDEX(prof_stats_bins_i)} | ||
| 568 | }; | ||
| 569 | |||
| 570 | static const ctl_named_node_t prof_stats_lextents_i_node[] = { | ||
| 571 | {NAME("live"), CTL(prof_stats_lextents_i_live)}, | ||
| 572 | {NAME("accum"), CTL(prof_stats_lextents_i_accum)} | ||
| 573 | }; | ||
| 574 | |||
| 575 | static const ctl_named_node_t super_prof_stats_lextents_i_node[] = { | ||
| 576 | {NAME(""), CHILD(named, prof_stats_lextents_i)} | ||
| 577 | }; | ||
| 578 | |||
| 579 | static const ctl_indexed_node_t prof_stats_lextents_node[] = { | ||
| 580 | {INDEX(prof_stats_lextents_i)} | ||
| 581 | }; | ||
| 582 | |||
| 583 | static const ctl_named_node_t prof_stats_node[] = { | ||
| 584 | {NAME("bins"), CHILD(indexed, prof_stats_bins)}, | ||
| 585 | {NAME("lextents"), CHILD(indexed, prof_stats_lextents)}, | ||
| 586 | }; | ||
| 587 | |||
| 588 | static const ctl_named_node_t prof_node[] = { | ||
| 589 | {NAME("thread_active_init"), CTL(prof_thread_active_init)}, | ||
| 590 | {NAME("active"), CTL(prof_active)}, | ||
| 591 | {NAME("dump"), CTL(prof_dump)}, | ||
| 592 | {NAME("gdump"), CTL(prof_gdump)}, | ||
| 593 | {NAME("prefix"), CTL(prof_prefix)}, | ||
| 594 | {NAME("reset"), CTL(prof_reset)}, | ||
| 595 | {NAME("interval"), CTL(prof_interval)}, | ||
| 596 | {NAME("lg_sample"), CTL(lg_prof_sample)}, | ||
| 597 | {NAME("log_start"), CTL(prof_log_start)}, | ||
| 598 | {NAME("log_stop"), CTL(prof_log_stop)}, | ||
| 599 | {NAME("stats"), CHILD(named, prof_stats)} | ||
| 600 | }; | ||
| 601 | |||
| 602 | static const ctl_named_node_t stats_arenas_i_small_node[] = { | ||
| 603 | {NAME("allocated"), CTL(stats_arenas_i_small_allocated)}, | ||
| 604 | {NAME("nmalloc"), CTL(stats_arenas_i_small_nmalloc)}, | ||
| 605 | {NAME("ndalloc"), CTL(stats_arenas_i_small_ndalloc)}, | ||
| 606 | {NAME("nrequests"), CTL(stats_arenas_i_small_nrequests)}, | ||
| 607 | {NAME("nfills"), CTL(stats_arenas_i_small_nfills)}, | ||
| 608 | {NAME("nflushes"), CTL(stats_arenas_i_small_nflushes)} | ||
| 609 | }; | ||
| 610 | |||
| 611 | static const ctl_named_node_t stats_arenas_i_large_node[] = { | ||
| 612 | {NAME("allocated"), CTL(stats_arenas_i_large_allocated)}, | ||
| 613 | {NAME("nmalloc"), CTL(stats_arenas_i_large_nmalloc)}, | ||
| 614 | {NAME("ndalloc"), CTL(stats_arenas_i_large_ndalloc)}, | ||
| 615 | {NAME("nrequests"), CTL(stats_arenas_i_large_nrequests)}, | ||
| 616 | {NAME("nfills"), CTL(stats_arenas_i_large_nfills)}, | ||
| 617 | {NAME("nflushes"), CTL(stats_arenas_i_large_nflushes)} | ||
| 618 | }; | ||
| 619 | |||
| 620 | #define MUTEX_PROF_DATA_NODE(prefix) \ | ||
| 621 | static const ctl_named_node_t stats_##prefix##_node[] = { \ | ||
| 622 | {NAME("num_ops"), \ | ||
| 623 | CTL(stats_##prefix##_num_ops)}, \ | ||
| 624 | {NAME("num_wait"), \ | ||
| 625 | CTL(stats_##prefix##_num_wait)}, \ | ||
| 626 | {NAME("num_spin_acq"), \ | ||
| 627 | CTL(stats_##prefix##_num_spin_acq)}, \ | ||
| 628 | {NAME("num_owner_switch"), \ | ||
| 629 | CTL(stats_##prefix##_num_owner_switch)}, \ | ||
| 630 | {NAME("total_wait_time"), \ | ||
| 631 | CTL(stats_##prefix##_total_wait_time)}, \ | ||
| 632 | {NAME("max_wait_time"), \ | ||
| 633 | CTL(stats_##prefix##_max_wait_time)}, \ | ||
| 634 | {NAME("max_num_thds"), \ | ||
| 635 | CTL(stats_##prefix##_max_num_thds)} \ | ||
| 636 | /* Note that # of current waiting thread not provided. */ \ | ||
| 637 | }; | ||
| 638 | |||
| 639 | MUTEX_PROF_DATA_NODE(arenas_i_bins_j_mutex) | ||
| 640 | |||
| 641 | static const ctl_named_node_t stats_arenas_i_bins_j_node[] = { | ||
| 642 | {NAME("nmalloc"), CTL(stats_arenas_i_bins_j_nmalloc)}, | ||
| 643 | {NAME("ndalloc"), CTL(stats_arenas_i_bins_j_ndalloc)}, | ||
| 644 | {NAME("nrequests"), CTL(stats_arenas_i_bins_j_nrequests)}, | ||
| 645 | {NAME("curregs"), CTL(stats_arenas_i_bins_j_curregs)}, | ||
| 646 | {NAME("nfills"), CTL(stats_arenas_i_bins_j_nfills)}, | ||
| 647 | {NAME("nflushes"), CTL(stats_arenas_i_bins_j_nflushes)}, | ||
| 648 | {NAME("nslabs"), CTL(stats_arenas_i_bins_j_nslabs)}, | ||
| 649 | {NAME("nreslabs"), CTL(stats_arenas_i_bins_j_nreslabs)}, | ||
| 650 | {NAME("curslabs"), CTL(stats_arenas_i_bins_j_curslabs)}, | ||
| 651 | {NAME("nonfull_slabs"), CTL(stats_arenas_i_bins_j_nonfull_slabs)}, | ||
| 652 | {NAME("mutex"), CHILD(named, stats_arenas_i_bins_j_mutex)} | ||
| 653 | }; | ||
| 654 | |||
| 655 | static const ctl_named_node_t super_stats_arenas_i_bins_j_node[] = { | ||
| 656 | {NAME(""), CHILD(named, stats_arenas_i_bins_j)} | ||
| 657 | }; | ||
| 658 | |||
| 659 | static const ctl_indexed_node_t stats_arenas_i_bins_node[] = { | ||
| 660 | {INDEX(stats_arenas_i_bins_j)} | ||
| 661 | }; | ||
| 662 | |||
| 663 | static const ctl_named_node_t stats_arenas_i_lextents_j_node[] = { | ||
| 664 | {NAME("nmalloc"), CTL(stats_arenas_i_lextents_j_nmalloc)}, | ||
| 665 | {NAME("ndalloc"), CTL(stats_arenas_i_lextents_j_ndalloc)}, | ||
| 666 | {NAME("nrequests"), CTL(stats_arenas_i_lextents_j_nrequests)}, | ||
| 667 | {NAME("curlextents"), CTL(stats_arenas_i_lextents_j_curlextents)} | ||
| 668 | }; | ||
| 669 | static const ctl_named_node_t super_stats_arenas_i_lextents_j_node[] = { | ||
| 670 | {NAME(""), CHILD(named, stats_arenas_i_lextents_j)} | ||
| 671 | }; | ||
| 672 | |||
| 673 | static const ctl_indexed_node_t stats_arenas_i_lextents_node[] = { | ||
| 674 | {INDEX(stats_arenas_i_lextents_j)} | ||
| 675 | }; | ||
| 676 | |||
| 677 | static const ctl_named_node_t stats_arenas_i_extents_j_node[] = { | ||
| 678 | {NAME("ndirty"), CTL(stats_arenas_i_extents_j_ndirty)}, | ||
| 679 | {NAME("nmuzzy"), CTL(stats_arenas_i_extents_j_nmuzzy)}, | ||
| 680 | {NAME("nretained"), CTL(stats_arenas_i_extents_j_nretained)}, | ||
| 681 | {NAME("dirty_bytes"), CTL(stats_arenas_i_extents_j_dirty_bytes)}, | ||
| 682 | {NAME("muzzy_bytes"), CTL(stats_arenas_i_extents_j_muzzy_bytes)}, | ||
| 683 | {NAME("retained_bytes"), CTL(stats_arenas_i_extents_j_retained_bytes)} | ||
| 684 | }; | ||
| 685 | |||
| 686 | static const ctl_named_node_t super_stats_arenas_i_extents_j_node[] = { | ||
| 687 | {NAME(""), CHILD(named, stats_arenas_i_extents_j)} | ||
| 688 | }; | ||
| 689 | |||
| 690 | static const ctl_indexed_node_t stats_arenas_i_extents_node[] = { | ||
| 691 | {INDEX(stats_arenas_i_extents_j)} | ||
| 692 | }; | ||
| 693 | |||
| 694 | #define OP(mtx) MUTEX_PROF_DATA_NODE(arenas_i_mutexes_##mtx) | ||
| 695 | MUTEX_PROF_ARENA_MUTEXES | ||
| 696 | #undef OP | ||
| 697 | |||
| 698 | static const ctl_named_node_t stats_arenas_i_mutexes_node[] = { | ||
| 699 | #define OP(mtx) {NAME(#mtx), CHILD(named, stats_arenas_i_mutexes_##mtx)}, | ||
| 700 | MUTEX_PROF_ARENA_MUTEXES | ||
| 701 | #undef OP | ||
| 702 | }; | ||
| 703 | |||
| 704 | static const ctl_named_node_t stats_arenas_i_hpa_shard_full_slabs_node[] = { | ||
| 705 | {NAME("npageslabs_nonhuge"), | ||
| 706 | CTL(stats_arenas_i_hpa_shard_full_slabs_npageslabs_nonhuge)}, | ||
| 707 | {NAME("npageslabs_huge"), | ||
| 708 | CTL(stats_arenas_i_hpa_shard_full_slabs_npageslabs_huge)}, | ||
| 709 | {NAME("nactive_nonhuge"), | ||
| 710 | CTL(stats_arenas_i_hpa_shard_full_slabs_nactive_nonhuge)}, | ||
| 711 | {NAME("nactive_huge"), | ||
| 712 | CTL(stats_arenas_i_hpa_shard_full_slabs_nactive_huge)}, | ||
| 713 | {NAME("ndirty_nonhuge"), | ||
| 714 | CTL(stats_arenas_i_hpa_shard_full_slabs_ndirty_nonhuge)}, | ||
| 715 | {NAME("ndirty_huge"), | ||
| 716 | CTL(stats_arenas_i_hpa_shard_full_slabs_ndirty_huge)} | ||
| 717 | }; | ||
| 718 | |||
| 719 | static const ctl_named_node_t stats_arenas_i_hpa_shard_empty_slabs_node[] = { | ||
| 720 | {NAME("npageslabs_nonhuge"), | ||
| 721 | CTL(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_nonhuge)}, | ||
| 722 | {NAME("npageslabs_huge"), | ||
| 723 | CTL(stats_arenas_i_hpa_shard_empty_slabs_npageslabs_huge)}, | ||
| 724 | {NAME("nactive_nonhuge"), | ||
| 725 | CTL(stats_arenas_i_hpa_shard_empty_slabs_nactive_nonhuge)}, | ||
| 726 | {NAME("nactive_huge"), | ||
| 727 | CTL(stats_arenas_i_hpa_shard_empty_slabs_nactive_huge)}, | ||
| 728 | {NAME("ndirty_nonhuge"), | ||
| 729 | CTL(stats_arenas_i_hpa_shard_empty_slabs_ndirty_nonhuge)}, | ||
| 730 | {NAME("ndirty_huge"), | ||
| 731 | CTL(stats_arenas_i_hpa_shard_empty_slabs_ndirty_huge)} | ||
| 732 | }; | ||
| 733 | |||
| 734 | static const ctl_named_node_t stats_arenas_i_hpa_shard_nonfull_slabs_j_node[] = { | ||
| 735 | {NAME("npageslabs_nonhuge"), | ||
| 736 | CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_nonhuge)}, | ||
| 737 | {NAME("npageslabs_huge"), | ||
| 738 | CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_huge)}, | ||
| 739 | {NAME("nactive_nonhuge"), | ||
| 740 | CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_nonhuge)}, | ||
| 741 | {NAME("nactive_huge"), | ||
| 742 | CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_huge)}, | ||
| 743 | {NAME("ndirty_nonhuge"), | ||
| 744 | CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_nonhuge)}, | ||
| 745 | {NAME("ndirty_huge"), | ||
| 746 | CTL(stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_huge)} | ||
| 747 | }; | ||
| 748 | |||
| 749 | static const ctl_named_node_t super_stats_arenas_i_hpa_shard_nonfull_slabs_j_node[] = { | ||
| 750 | {NAME(""), | ||
| 751 | CHILD(named, stats_arenas_i_hpa_shard_nonfull_slabs_j)} | ||
| 752 | }; | ||
| 753 | |||
| 754 | static const ctl_indexed_node_t stats_arenas_i_hpa_shard_nonfull_slabs_node[] = | ||
| 755 | { | ||
| 756 | {INDEX(stats_arenas_i_hpa_shard_nonfull_slabs_j)} | ||
| 757 | }; | ||
| 758 | |||
| 759 | static const ctl_named_node_t stats_arenas_i_hpa_shard_node[] = { | ||
| 760 | {NAME("full_slabs"), CHILD(named, | ||
| 761 | stats_arenas_i_hpa_shard_full_slabs)}, | ||
| 762 | {NAME("empty_slabs"), CHILD(named, | ||
| 763 | stats_arenas_i_hpa_shard_empty_slabs)}, | ||
| 764 | {NAME("nonfull_slabs"), CHILD(indexed, | ||
| 765 | stats_arenas_i_hpa_shard_nonfull_slabs)}, | ||
| 766 | |||
| 767 | {NAME("npurge_passes"), CTL(stats_arenas_i_hpa_shard_npurge_passes)}, | ||
| 768 | {NAME("npurges"), CTL(stats_arenas_i_hpa_shard_npurges)}, | ||
| 769 | {NAME("nhugifies"), CTL(stats_arenas_i_hpa_shard_nhugifies)}, | ||
| 770 | {NAME("ndehugifies"), CTL(stats_arenas_i_hpa_shard_ndehugifies)} | ||
| 771 | }; | ||
| 772 | |||
| 773 | static const ctl_named_node_t stats_arenas_i_node[] = { | ||
| 774 | {NAME("nthreads"), CTL(stats_arenas_i_nthreads)}, | ||
| 775 | {NAME("uptime"), CTL(stats_arenas_i_uptime)}, | ||
| 776 | {NAME("dss"), CTL(stats_arenas_i_dss)}, | ||
| 777 | {NAME("dirty_decay_ms"), CTL(stats_arenas_i_dirty_decay_ms)}, | ||
| 778 | {NAME("muzzy_decay_ms"), CTL(stats_arenas_i_muzzy_decay_ms)}, | ||
| 779 | {NAME("pactive"), CTL(stats_arenas_i_pactive)}, | ||
| 780 | {NAME("pdirty"), CTL(stats_arenas_i_pdirty)}, | ||
| 781 | {NAME("pmuzzy"), CTL(stats_arenas_i_pmuzzy)}, | ||
| 782 | {NAME("mapped"), CTL(stats_arenas_i_mapped)}, | ||
| 783 | {NAME("retained"), CTL(stats_arenas_i_retained)}, | ||
| 784 | {NAME("extent_avail"), CTL(stats_arenas_i_extent_avail)}, | ||
| 785 | {NAME("dirty_npurge"), CTL(stats_arenas_i_dirty_npurge)}, | ||
| 786 | {NAME("dirty_nmadvise"), CTL(stats_arenas_i_dirty_nmadvise)}, | ||
| 787 | {NAME("dirty_purged"), CTL(stats_arenas_i_dirty_purged)}, | ||
| 788 | {NAME("muzzy_npurge"), CTL(stats_arenas_i_muzzy_npurge)}, | ||
| 789 | {NAME("muzzy_nmadvise"), CTL(stats_arenas_i_muzzy_nmadvise)}, | ||
| 790 | {NAME("muzzy_purged"), CTL(stats_arenas_i_muzzy_purged)}, | ||
| 791 | {NAME("base"), CTL(stats_arenas_i_base)}, | ||
| 792 | {NAME("internal"), CTL(stats_arenas_i_internal)}, | ||
| 793 | {NAME("metadata_thp"), CTL(stats_arenas_i_metadata_thp)}, | ||
| 794 | {NAME("tcache_bytes"), CTL(stats_arenas_i_tcache_bytes)}, | ||
| 795 | {NAME("tcache_stashed_bytes"), | ||
| 796 | CTL(stats_arenas_i_tcache_stashed_bytes)}, | ||
| 797 | {NAME("resident"), CTL(stats_arenas_i_resident)}, | ||
| 798 | {NAME("abandoned_vm"), CTL(stats_arenas_i_abandoned_vm)}, | ||
| 799 | {NAME("hpa_sec_bytes"), CTL(stats_arenas_i_hpa_sec_bytes)}, | ||
| 800 | {NAME("small"), CHILD(named, stats_arenas_i_small)}, | ||
| 801 | {NAME("large"), CHILD(named, stats_arenas_i_large)}, | ||
| 802 | {NAME("bins"), CHILD(indexed, stats_arenas_i_bins)}, | ||
| 803 | {NAME("lextents"), CHILD(indexed, stats_arenas_i_lextents)}, | ||
| 804 | {NAME("extents"), CHILD(indexed, stats_arenas_i_extents)}, | ||
| 805 | {NAME("mutexes"), CHILD(named, stats_arenas_i_mutexes)}, | ||
| 806 | {NAME("hpa_shard"), CHILD(named, stats_arenas_i_hpa_shard)} | ||
| 807 | }; | ||
| 808 | static const ctl_named_node_t super_stats_arenas_i_node[] = { | ||
| 809 | {NAME(""), CHILD(named, stats_arenas_i)} | ||
| 810 | }; | ||
| 811 | |||
| 812 | static const ctl_indexed_node_t stats_arenas_node[] = { | ||
| 813 | {INDEX(stats_arenas_i)} | ||
| 814 | }; | ||
| 815 | |||
| 816 | static const ctl_named_node_t stats_background_thread_node[] = { | ||
| 817 | {NAME("num_threads"), CTL(stats_background_thread_num_threads)}, | ||
| 818 | {NAME("num_runs"), CTL(stats_background_thread_num_runs)}, | ||
| 819 | {NAME("run_interval"), CTL(stats_background_thread_run_interval)} | ||
| 820 | }; | ||
| 821 | |||
| 822 | #define OP(mtx) MUTEX_PROF_DATA_NODE(mutexes_##mtx) | ||
| 823 | MUTEX_PROF_GLOBAL_MUTEXES | ||
| 824 | #undef OP | ||
| 825 | |||
| 826 | static const ctl_named_node_t stats_mutexes_node[] = { | ||
| 827 | #define OP(mtx) {NAME(#mtx), CHILD(named, stats_mutexes_##mtx)}, | ||
| 828 | MUTEX_PROF_GLOBAL_MUTEXES | ||
| 829 | #undef OP | ||
| 830 | {NAME("reset"), CTL(stats_mutexes_reset)} | ||
| 831 | }; | ||
| 832 | #undef MUTEX_PROF_DATA_NODE | ||
| 833 | |||
| 834 | static const ctl_named_node_t stats_node[] = { | ||
| 835 | {NAME("allocated"), CTL(stats_allocated)}, | ||
| 836 | {NAME("active"), CTL(stats_active)}, | ||
| 837 | {NAME("metadata"), CTL(stats_metadata)}, | ||
| 838 | {NAME("metadata_thp"), CTL(stats_metadata_thp)}, | ||
| 839 | {NAME("resident"), CTL(stats_resident)}, | ||
| 840 | {NAME("mapped"), CTL(stats_mapped)}, | ||
| 841 | {NAME("retained"), CTL(stats_retained)}, | ||
| 842 | {NAME("background_thread"), | ||
| 843 | CHILD(named, stats_background_thread)}, | ||
| 844 | {NAME("mutexes"), CHILD(named, stats_mutexes)}, | ||
| 845 | {NAME("arenas"), CHILD(indexed, stats_arenas)}, | ||
| 846 | {NAME("zero_reallocs"), CTL(stats_zero_reallocs)}, | ||
| 847 | }; | ||
| 848 | |||
| 849 | static const ctl_named_node_t experimental_hooks_node[] = { | ||
| 850 | {NAME("install"), CTL(experimental_hooks_install)}, | ||
| 851 | {NAME("remove"), CTL(experimental_hooks_remove)}, | ||
| 852 | {NAME("prof_backtrace"), CTL(experimental_hooks_prof_backtrace)}, | ||
| 853 | {NAME("prof_dump"), CTL(experimental_hooks_prof_dump)}, | ||
| 854 | {NAME("safety_check_abort"), CTL(experimental_hooks_safety_check_abort)}, | ||
| 855 | }; | ||
| 856 | |||
| 857 | static const ctl_named_node_t experimental_thread_node[] = { | ||
| 858 | {NAME("activity_callback"), | ||
| 859 | CTL(experimental_thread_activity_callback)} | ||
| 860 | }; | ||
| 861 | |||
| 862 | static const ctl_named_node_t experimental_utilization_node[] = { | ||
| 863 | {NAME("query"), CTL(experimental_utilization_query)}, | ||
| 864 | {NAME("batch_query"), CTL(experimental_utilization_batch_query)} | ||
| 865 | }; | ||
| 866 | |||
| 867 | static const ctl_named_node_t experimental_arenas_i_node[] = { | ||
| 868 | {NAME("pactivep"), CTL(experimental_arenas_i_pactivep)} | ||
| 869 | }; | ||
| 870 | static const ctl_named_node_t super_experimental_arenas_i_node[] = { | ||
| 871 | {NAME(""), CHILD(named, experimental_arenas_i)} | ||
| 872 | }; | ||
| 873 | |||
| 874 | static const ctl_indexed_node_t experimental_arenas_node[] = { | ||
| 875 | {INDEX(experimental_arenas_i)} | ||
| 876 | }; | ||
| 877 | |||
| 878 | static const ctl_named_node_t experimental_prof_recent_node[] = { | ||
| 879 | {NAME("alloc_max"), CTL(experimental_prof_recent_alloc_max)}, | ||
| 880 | {NAME("alloc_dump"), CTL(experimental_prof_recent_alloc_dump)}, | ||
| 881 | }; | ||
| 882 | |||
| 883 | static const ctl_named_node_t experimental_node[] = { | ||
| 884 | {NAME("hooks"), CHILD(named, experimental_hooks)}, | ||
| 885 | {NAME("utilization"), CHILD(named, experimental_utilization)}, | ||
| 886 | {NAME("arenas"), CHILD(indexed, experimental_arenas)}, | ||
| 887 | {NAME("arenas_create_ext"), CTL(experimental_arenas_create_ext)}, | ||
| 888 | {NAME("prof_recent"), CHILD(named, experimental_prof_recent)}, | ||
| 889 | {NAME("batch_alloc"), CTL(experimental_batch_alloc)}, | ||
| 890 | {NAME("thread"), CHILD(named, experimental_thread)} | ||
| 891 | }; | ||
| 892 | |||
| 893 | static const ctl_named_node_t root_node[] = { | ||
| 894 | {NAME("version"), CTL(version)}, | ||
| 895 | {NAME("epoch"), CTL(epoch)}, | ||
| 896 | {NAME("background_thread"), CTL(background_thread)}, | ||
| 897 | {NAME("max_background_threads"), CTL(max_background_threads)}, | ||
| 898 | {NAME("thread"), CHILD(named, thread)}, | ||
| 899 | {NAME("config"), CHILD(named, config)}, | ||
| 900 | {NAME("opt"), CHILD(named, opt)}, | ||
| 901 | {NAME("tcache"), CHILD(named, tcache)}, | ||
| 902 | {NAME("arena"), CHILD(indexed, arena)}, | ||
| 903 | {NAME("arenas"), CHILD(named, arenas)}, | ||
| 904 | {NAME("prof"), CHILD(named, prof)}, | ||
| 905 | {NAME("stats"), CHILD(named, stats)}, | ||
| 906 | {NAME("experimental"), CHILD(named, experimental)} | ||
| 907 | }; | ||
| 908 | static const ctl_named_node_t super_root_node[] = { | ||
| 909 | {NAME(""), CHILD(named, root)} | ||
| 910 | }; | ||
| 911 | |||
| 912 | #undef NAME | ||
| 913 | #undef CHILD | ||
| 914 | #undef CTL | ||
| 915 | #undef INDEX | ||
| 916 | |||
| 917 | /******************************************************************************/ | ||
| 918 | |||
| 919 | /* | ||
| 920 | * Sets *dst + *src non-atomically. This is safe, since everything is | ||
| 921 | * synchronized by the ctl mutex. | ||
| 922 | */ | ||
| 923 | static void | ||
| 924 | ctl_accum_locked_u64(locked_u64_t *dst, locked_u64_t *src) { | ||
| 925 | locked_inc_u64_unsynchronized(dst, | ||
| 926 | locked_read_u64_unsynchronized(src)); | ||
| 927 | } | ||
| 928 | |||
| 929 | static void | ||
| 930 | ctl_accum_atomic_zu(atomic_zu_t *dst, atomic_zu_t *src) { | ||
| 931 | size_t cur_dst = atomic_load_zu(dst, ATOMIC_RELAXED); | ||
| 932 | size_t cur_src = atomic_load_zu(src, ATOMIC_RELAXED); | ||
| 933 | atomic_store_zu(dst, cur_dst + cur_src, ATOMIC_RELAXED); | ||
| 934 | } | ||
| 935 | |||
| 936 | /******************************************************************************/ | ||
| 937 | |||
| 938 | static unsigned | ||
| 939 | arenas_i2a_impl(size_t i, bool compat, bool validate) { | ||
| 940 | unsigned a; | ||
| 941 | |||
| 942 | switch (i) { | ||
| 943 | case MALLCTL_ARENAS_ALL: | ||
| 944 | a = 0; | ||
| 945 | break; | ||
| 946 | case MALLCTL_ARENAS_DESTROYED: | ||
| 947 | a = 1; | ||
| 948 | break; | ||
| 949 | default: | ||
| 950 | if (compat && i == ctl_arenas->narenas) { | ||
| 951 | /* | ||
| 952 | * Provide deprecated backward compatibility for | ||
| 953 | * accessing the merged stats at index narenas rather | ||
| 954 | * than via MALLCTL_ARENAS_ALL. This is scheduled for | ||
| 955 | * removal in 6.0.0. | ||
| 956 | */ | ||
| 957 | a = 0; | ||
| 958 | } else if (validate && i >= ctl_arenas->narenas) { | ||
| 959 | a = UINT_MAX; | ||
| 960 | } else { | ||
| 961 | /* | ||
| 962 | * This function should never be called for an index | ||
| 963 | * more than one past the range of indices that have | ||
| 964 | * initialized ctl data. | ||
| 965 | */ | ||
| 966 | assert(i < ctl_arenas->narenas || (!validate && i == | ||
| 967 | ctl_arenas->narenas)); | ||
| 968 | a = (unsigned)i + 2; | ||
| 969 | } | ||
| 970 | break; | ||
| 971 | } | ||
| 972 | |||
| 973 | return a; | ||
| 974 | } | ||
| 975 | |||
| 976 | static unsigned | ||
| 977 | arenas_i2a(size_t i) { | ||
| 978 | return arenas_i2a_impl(i, true, false); | ||
| 979 | } | ||
| 980 | |||
| 981 | static ctl_arena_t * | ||
| 982 | arenas_i_impl(tsd_t *tsd, size_t i, bool compat, bool init) { | ||
| 983 | ctl_arena_t *ret; | ||
| 984 | |||
| 985 | assert(!compat || !init); | ||
| 986 | |||
| 987 | ret = ctl_arenas->arenas[arenas_i2a_impl(i, compat, false)]; | ||
| 988 | if (init && ret == NULL) { | ||
| 989 | if (config_stats) { | ||
| 990 | struct container_s { | ||
| 991 | ctl_arena_t ctl_arena; | ||
| 992 | ctl_arena_stats_t astats; | ||
| 993 | }; | ||
| 994 | struct container_s *cont = | ||
| 995 | (struct container_s *)base_alloc(tsd_tsdn(tsd), | ||
| 996 | b0get(), sizeof(struct container_s), QUANTUM); | ||
| 997 | if (cont == NULL) { | ||
| 998 | return NULL; | ||
| 999 | } | ||
| 1000 | ret = &cont->ctl_arena; | ||
| 1001 | ret->astats = &cont->astats; | ||
| 1002 | } else { | ||
| 1003 | ret = (ctl_arena_t *)base_alloc(tsd_tsdn(tsd), b0get(), | ||
| 1004 | sizeof(ctl_arena_t), QUANTUM); | ||
| 1005 | if (ret == NULL) { | ||
| 1006 | return NULL; | ||
| 1007 | } | ||
| 1008 | } | ||
| 1009 | ret->arena_ind = (unsigned)i; | ||
| 1010 | ctl_arenas->arenas[arenas_i2a_impl(i, compat, false)] = ret; | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | assert(ret == NULL || arenas_i2a(ret->arena_ind) == arenas_i2a(i)); | ||
| 1014 | return ret; | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | static ctl_arena_t * | ||
| 1018 | arenas_i(size_t i) { | ||
| 1019 | ctl_arena_t *ret = arenas_i_impl(tsd_fetch(), i, true, false); | ||
| 1020 | assert(ret != NULL); | ||
| 1021 | return ret; | ||
| 1022 | } | ||
| 1023 | |||
| 1024 | static void | ||
| 1025 | ctl_arena_clear(ctl_arena_t *ctl_arena) { | ||
| 1026 | ctl_arena->nthreads = 0; | ||
| 1027 | ctl_arena->dss = dss_prec_names[dss_prec_limit]; | ||
| 1028 | ctl_arena->dirty_decay_ms = -1; | ||
| 1029 | ctl_arena->muzzy_decay_ms = -1; | ||
| 1030 | ctl_arena->pactive = 0; | ||
| 1031 | ctl_arena->pdirty = 0; | ||
| 1032 | ctl_arena->pmuzzy = 0; | ||
| 1033 | if (config_stats) { | ||
| 1034 | memset(&ctl_arena->astats->astats, 0, sizeof(arena_stats_t)); | ||
| 1035 | ctl_arena->astats->allocated_small = 0; | ||
| 1036 | ctl_arena->astats->nmalloc_small = 0; | ||
| 1037 | ctl_arena->astats->ndalloc_small = 0; | ||
| 1038 | ctl_arena->astats->nrequests_small = 0; | ||
| 1039 | ctl_arena->astats->nfills_small = 0; | ||
| 1040 | ctl_arena->astats->nflushes_small = 0; | ||
| 1041 | memset(ctl_arena->astats->bstats, 0, SC_NBINS * | ||
| 1042 | sizeof(bin_stats_data_t)); | ||
| 1043 | memset(ctl_arena->astats->lstats, 0, (SC_NSIZES - SC_NBINS) * | ||
| 1044 | sizeof(arena_stats_large_t)); | ||
| 1045 | memset(ctl_arena->astats->estats, 0, SC_NPSIZES * | ||
| 1046 | sizeof(pac_estats_t)); | ||
| 1047 | memset(&ctl_arena->astats->hpastats, 0, | ||
| 1048 | sizeof(hpa_shard_stats_t)); | ||
| 1049 | memset(&ctl_arena->astats->secstats, 0, | ||
| 1050 | sizeof(sec_stats_t)); | ||
| 1051 | } | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | static void | ||
| 1055 | ctl_arena_stats_amerge(tsdn_t *tsdn, ctl_arena_t *ctl_arena, arena_t *arena) { | ||
| 1056 | unsigned i; | ||
| 1057 | |||
| 1058 | if (config_stats) { | ||
| 1059 | arena_stats_merge(tsdn, arena, &ctl_arena->nthreads, | ||
| 1060 | &ctl_arena->dss, &ctl_arena->dirty_decay_ms, | ||
| 1061 | &ctl_arena->muzzy_decay_ms, &ctl_arena->pactive, | ||
| 1062 | &ctl_arena->pdirty, &ctl_arena->pmuzzy, | ||
| 1063 | &ctl_arena->astats->astats, ctl_arena->astats->bstats, | ||
| 1064 | ctl_arena->astats->lstats, ctl_arena->astats->estats, | ||
| 1065 | &ctl_arena->astats->hpastats, &ctl_arena->astats->secstats); | ||
| 1066 | |||
| 1067 | for (i = 0; i < SC_NBINS; i++) { | ||
| 1068 | bin_stats_t *bstats = | ||
| 1069 | &ctl_arena->astats->bstats[i].stats_data; | ||
| 1070 | ctl_arena->astats->allocated_small += bstats->curregs * | ||
| 1071 | sz_index2size(i); | ||
| 1072 | ctl_arena->astats->nmalloc_small += bstats->nmalloc; | ||
| 1073 | ctl_arena->astats->ndalloc_small += bstats->ndalloc; | ||
| 1074 | ctl_arena->astats->nrequests_small += bstats->nrequests; | ||
| 1075 | ctl_arena->astats->nfills_small += bstats->nfills; | ||
| 1076 | ctl_arena->astats->nflushes_small += bstats->nflushes; | ||
| 1077 | } | ||
| 1078 | } else { | ||
| 1079 | arena_basic_stats_merge(tsdn, arena, &ctl_arena->nthreads, | ||
| 1080 | &ctl_arena->dss, &ctl_arena->dirty_decay_ms, | ||
| 1081 | &ctl_arena->muzzy_decay_ms, &ctl_arena->pactive, | ||
| 1082 | &ctl_arena->pdirty, &ctl_arena->pmuzzy); | ||
| 1083 | } | ||
| 1084 | } | ||
| 1085 | |||
| 1086 | static void | ||
| 1087 | ctl_arena_stats_sdmerge(ctl_arena_t *ctl_sdarena, ctl_arena_t *ctl_arena, | ||
| 1088 | bool destroyed) { | ||
| 1089 | unsigned i; | ||
| 1090 | |||
| 1091 | if (!destroyed) { | ||
| 1092 | ctl_sdarena->nthreads += ctl_arena->nthreads; | ||
| 1093 | ctl_sdarena->pactive += ctl_arena->pactive; | ||
| 1094 | ctl_sdarena->pdirty += ctl_arena->pdirty; | ||
| 1095 | ctl_sdarena->pmuzzy += ctl_arena->pmuzzy; | ||
| 1096 | } else { | ||
| 1097 | assert(ctl_arena->nthreads == 0); | ||
| 1098 | assert(ctl_arena->pactive == 0); | ||
| 1099 | assert(ctl_arena->pdirty == 0); | ||
| 1100 | assert(ctl_arena->pmuzzy == 0); | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | if (config_stats) { | ||
| 1104 | ctl_arena_stats_t *sdstats = ctl_sdarena->astats; | ||
| 1105 | ctl_arena_stats_t *astats = ctl_arena->astats; | ||
| 1106 | |||
| 1107 | if (!destroyed) { | ||
| 1108 | sdstats->astats.mapped += astats->astats.mapped; | ||
| 1109 | sdstats->astats.pa_shard_stats.pac_stats.retained | ||
| 1110 | += astats->astats.pa_shard_stats.pac_stats.retained; | ||
| 1111 | sdstats->astats.pa_shard_stats.edata_avail | ||
| 1112 | += astats->astats.pa_shard_stats.edata_avail; | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | ctl_accum_locked_u64( | ||
| 1116 | &sdstats->astats.pa_shard_stats.pac_stats.decay_dirty.npurge, | ||
| 1117 | &astats->astats.pa_shard_stats.pac_stats.decay_dirty.npurge); | ||
| 1118 | ctl_accum_locked_u64( | ||
| 1119 | &sdstats->astats.pa_shard_stats.pac_stats.decay_dirty.nmadvise, | ||
| 1120 | &astats->astats.pa_shard_stats.pac_stats.decay_dirty.nmadvise); | ||
| 1121 | ctl_accum_locked_u64( | ||
| 1122 | &sdstats->astats.pa_shard_stats.pac_stats.decay_dirty.purged, | ||
| 1123 | &astats->astats.pa_shard_stats.pac_stats.decay_dirty.purged); | ||
| 1124 | |||
| 1125 | ctl_accum_locked_u64( | ||
| 1126 | &sdstats->astats.pa_shard_stats.pac_stats.decay_muzzy.npurge, | ||
| 1127 | &astats->astats.pa_shard_stats.pac_stats.decay_muzzy.npurge); | ||
| 1128 | ctl_accum_locked_u64( | ||
| 1129 | &sdstats->astats.pa_shard_stats.pac_stats.decay_muzzy.nmadvise, | ||
| 1130 | &astats->astats.pa_shard_stats.pac_stats.decay_muzzy.nmadvise); | ||
| 1131 | ctl_accum_locked_u64( | ||
| 1132 | &sdstats->astats.pa_shard_stats.pac_stats.decay_muzzy.purged, | ||
| 1133 | &astats->astats.pa_shard_stats.pac_stats.decay_muzzy.purged); | ||
| 1134 | |||
| 1135 | #define OP(mtx) malloc_mutex_prof_merge( \ | ||
| 1136 | &(sdstats->astats.mutex_prof_data[ \ | ||
| 1137 | arena_prof_mutex_##mtx]), \ | ||
| 1138 | &(astats->astats.mutex_prof_data[ \ | ||
| 1139 | arena_prof_mutex_##mtx])); | ||
| 1140 | MUTEX_PROF_ARENA_MUTEXES | ||
| 1141 | #undef OP | ||
| 1142 | if (!destroyed) { | ||
| 1143 | sdstats->astats.base += astats->astats.base; | ||
| 1144 | sdstats->astats.resident += astats->astats.resident; | ||
| 1145 | sdstats->astats.metadata_thp += astats->astats.metadata_thp; | ||
| 1146 | ctl_accum_atomic_zu(&sdstats->astats.internal, | ||
| 1147 | &astats->astats.internal); | ||
| 1148 | } else { | ||
| 1149 | assert(atomic_load_zu( | ||
| 1150 | &astats->astats.internal, ATOMIC_RELAXED) == 0); | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | if (!destroyed) { | ||
| 1154 | sdstats->allocated_small += astats->allocated_small; | ||
| 1155 | } else { | ||
| 1156 | assert(astats->allocated_small == 0); | ||
| 1157 | } | ||
| 1158 | sdstats->nmalloc_small += astats->nmalloc_small; | ||
| 1159 | sdstats->ndalloc_small += astats->ndalloc_small; | ||
| 1160 | sdstats->nrequests_small += astats->nrequests_small; | ||
| 1161 | sdstats->nfills_small += astats->nfills_small; | ||
| 1162 | sdstats->nflushes_small += astats->nflushes_small; | ||
| 1163 | |||
| 1164 | if (!destroyed) { | ||
| 1165 | sdstats->astats.allocated_large += | ||
| 1166 | astats->astats.allocated_large; | ||
| 1167 | } else { | ||
| 1168 | assert(astats->astats.allocated_large == 0); | ||
| 1169 | } | ||
| 1170 | sdstats->astats.nmalloc_large += astats->astats.nmalloc_large; | ||
| 1171 | sdstats->astats.ndalloc_large += astats->astats.ndalloc_large; | ||
| 1172 | sdstats->astats.nrequests_large | ||
| 1173 | += astats->astats.nrequests_large; | ||
| 1174 | sdstats->astats.nflushes_large += astats->astats.nflushes_large; | ||
| 1175 | ctl_accum_atomic_zu( | ||
| 1176 | &sdstats->astats.pa_shard_stats.pac_stats.abandoned_vm, | ||
| 1177 | &astats->astats.pa_shard_stats.pac_stats.abandoned_vm); | ||
| 1178 | |||
| 1179 | sdstats->astats.tcache_bytes += astats->astats.tcache_bytes; | ||
| 1180 | sdstats->astats.tcache_stashed_bytes += | ||
| 1181 | astats->astats.tcache_stashed_bytes; | ||
| 1182 | |||
| 1183 | if (ctl_arena->arena_ind == 0) { | ||
| 1184 | sdstats->astats.uptime = astats->astats.uptime; | ||
| 1185 | } | ||
| 1186 | |||
| 1187 | /* Merge bin stats. */ | ||
| 1188 | for (i = 0; i < SC_NBINS; i++) { | ||
| 1189 | bin_stats_t *bstats = &astats->bstats[i].stats_data; | ||
| 1190 | bin_stats_t *merged = &sdstats->bstats[i].stats_data; | ||
| 1191 | merged->nmalloc += bstats->nmalloc; | ||
| 1192 | merged->ndalloc += bstats->ndalloc; | ||
| 1193 | merged->nrequests += bstats->nrequests; | ||
| 1194 | if (!destroyed) { | ||
| 1195 | merged->curregs += bstats->curregs; | ||
| 1196 | } else { | ||
| 1197 | assert(bstats->curregs == 0); | ||
| 1198 | } | ||
| 1199 | merged->nfills += bstats->nfills; | ||
| 1200 | merged->nflushes += bstats->nflushes; | ||
| 1201 | merged->nslabs += bstats->nslabs; | ||
| 1202 | merged->reslabs += bstats->reslabs; | ||
| 1203 | if (!destroyed) { | ||
| 1204 | merged->curslabs += bstats->curslabs; | ||
| 1205 | merged->nonfull_slabs += bstats->nonfull_slabs; | ||
| 1206 | } else { | ||
| 1207 | assert(bstats->curslabs == 0); | ||
| 1208 | assert(bstats->nonfull_slabs == 0); | ||
| 1209 | } | ||
| 1210 | malloc_mutex_prof_merge(&sdstats->bstats[i].mutex_data, | ||
| 1211 | &astats->bstats[i].mutex_data); | ||
| 1212 | } | ||
| 1213 | |||
| 1214 | /* Merge stats for large allocations. */ | ||
| 1215 | for (i = 0; i < SC_NSIZES - SC_NBINS; i++) { | ||
| 1216 | ctl_accum_locked_u64(&sdstats->lstats[i].nmalloc, | ||
| 1217 | &astats->lstats[i].nmalloc); | ||
| 1218 | ctl_accum_locked_u64(&sdstats->lstats[i].ndalloc, | ||
| 1219 | &astats->lstats[i].ndalloc); | ||
| 1220 | ctl_accum_locked_u64(&sdstats->lstats[i].nrequests, | ||
| 1221 | &astats->lstats[i].nrequests); | ||
| 1222 | if (!destroyed) { | ||
| 1223 | sdstats->lstats[i].curlextents += | ||
| 1224 | astats->lstats[i].curlextents; | ||
| 1225 | } else { | ||
| 1226 | assert(astats->lstats[i].curlextents == 0); | ||
| 1227 | } | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | /* Merge extents stats. */ | ||
| 1231 | for (i = 0; i < SC_NPSIZES; i++) { | ||
| 1232 | sdstats->estats[i].ndirty += astats->estats[i].ndirty; | ||
| 1233 | sdstats->estats[i].nmuzzy += astats->estats[i].nmuzzy; | ||
| 1234 | sdstats->estats[i].nretained | ||
| 1235 | += astats->estats[i].nretained; | ||
| 1236 | sdstats->estats[i].dirty_bytes | ||
| 1237 | += astats->estats[i].dirty_bytes; | ||
| 1238 | sdstats->estats[i].muzzy_bytes | ||
| 1239 | += astats->estats[i].muzzy_bytes; | ||
| 1240 | sdstats->estats[i].retained_bytes | ||
| 1241 | += astats->estats[i].retained_bytes; | ||
| 1242 | } | ||
| 1243 | |||
| 1244 | /* Merge HPA stats. */ | ||
| 1245 | hpa_shard_stats_accum(&sdstats->hpastats, &astats->hpastats); | ||
| 1246 | sec_stats_accum(&sdstats->secstats, &astats->secstats); | ||
| 1247 | } | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | static void | ||
| 1251 | ctl_arena_refresh(tsdn_t *tsdn, arena_t *arena, ctl_arena_t *ctl_sdarena, | ||
| 1252 | unsigned i, bool destroyed) { | ||
| 1253 | ctl_arena_t *ctl_arena = arenas_i(i); | ||
| 1254 | |||
| 1255 | ctl_arena_clear(ctl_arena); | ||
| 1256 | ctl_arena_stats_amerge(tsdn, ctl_arena, arena); | ||
| 1257 | /* Merge into sum stats as well. */ | ||
| 1258 | ctl_arena_stats_sdmerge(ctl_sdarena, ctl_arena, destroyed); | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | static unsigned | ||
| 1262 | ctl_arena_init(tsd_t *tsd, const arena_config_t *config) { | ||
| 1263 | unsigned arena_ind; | ||
| 1264 | ctl_arena_t *ctl_arena; | ||
| 1265 | |||
| 1266 | if ((ctl_arena = ql_last(&ctl_arenas->destroyed, destroyed_link)) != | ||
| 1267 | NULL) { | ||
| 1268 | ql_remove(&ctl_arenas->destroyed, ctl_arena, destroyed_link); | ||
| 1269 | arena_ind = ctl_arena->arena_ind; | ||
| 1270 | } else { | ||
| 1271 | arena_ind = ctl_arenas->narenas; | ||
| 1272 | } | ||
| 1273 | |||
| 1274 | /* Trigger stats allocation. */ | ||
| 1275 | if (arenas_i_impl(tsd, arena_ind, false, true) == NULL) { | ||
| 1276 | return UINT_MAX; | ||
| 1277 | } | ||
| 1278 | |||
| 1279 | /* Initialize new arena. */ | ||
| 1280 | if (arena_init(tsd_tsdn(tsd), arena_ind, config) == NULL) { | ||
| 1281 | return UINT_MAX; | ||
| 1282 | } | ||
| 1283 | |||
| 1284 | if (arena_ind == ctl_arenas->narenas) { | ||
| 1285 | ctl_arenas->narenas++; | ||
| 1286 | } | ||
| 1287 | |||
| 1288 | return arena_ind; | ||
| 1289 | } | ||
| 1290 | |||
| 1291 | static void | ||
| 1292 | ctl_background_thread_stats_read(tsdn_t *tsdn) { | ||
| 1293 | background_thread_stats_t *stats = &ctl_stats->background_thread; | ||
| 1294 | if (!have_background_thread || | ||
| 1295 | background_thread_stats_read(tsdn, stats)) { | ||
| 1296 | memset(stats, 0, sizeof(background_thread_stats_t)); | ||
| 1297 | nstime_init_zero(&stats->run_interval); | ||
| 1298 | } | ||
| 1299 | malloc_mutex_prof_copy( | ||
| 1300 | &ctl_stats->mutex_prof_data[global_prof_mutex_max_per_bg_thd], | ||
| 1301 | &stats->max_counter_per_bg_thd); | ||
| 1302 | } | ||
| 1303 | |||
| 1304 | static void | ||
| 1305 | ctl_refresh(tsdn_t *tsdn) { | ||
| 1306 | unsigned i; | ||
| 1307 | ctl_arena_t *ctl_sarena = arenas_i(MALLCTL_ARENAS_ALL); | ||
| 1308 | VARIABLE_ARRAY(arena_t *, tarenas, ctl_arenas->narenas); | ||
| 1309 | |||
| 1310 | /* | ||
| 1311 | * Clear sum stats, since they will be merged into by | ||
| 1312 | * ctl_arena_refresh(). | ||
| 1313 | */ | ||
| 1314 | ctl_arena_clear(ctl_sarena); | ||
| 1315 | |||
| 1316 | for (i = 0; i < ctl_arenas->narenas; i++) { | ||
| 1317 | tarenas[i] = arena_get(tsdn, i, false); | ||
| 1318 | } | ||
| 1319 | |||
| 1320 | for (i = 0; i < ctl_arenas->narenas; i++) { | ||
| 1321 | ctl_arena_t *ctl_arena = arenas_i(i); | ||
| 1322 | bool initialized = (tarenas[i] != NULL); | ||
| 1323 | |||
| 1324 | ctl_arena->initialized = initialized; | ||
| 1325 | if (initialized) { | ||
| 1326 | ctl_arena_refresh(tsdn, tarenas[i], ctl_sarena, i, | ||
| 1327 | false); | ||
| 1328 | } | ||
| 1329 | } | ||
| 1330 | |||
| 1331 | if (config_stats) { | ||
| 1332 | ctl_stats->allocated = ctl_sarena->astats->allocated_small + | ||
| 1333 | ctl_sarena->astats->astats.allocated_large; | ||
| 1334 | ctl_stats->active = (ctl_sarena->pactive << LG_PAGE); | ||
| 1335 | ctl_stats->metadata = ctl_sarena->astats->astats.base + | ||
| 1336 | atomic_load_zu(&ctl_sarena->astats->astats.internal, | ||
| 1337 | ATOMIC_RELAXED); | ||
| 1338 | ctl_stats->resident = ctl_sarena->astats->astats.resident; | ||
| 1339 | ctl_stats->metadata_thp = | ||
| 1340 | ctl_sarena->astats->astats.metadata_thp; | ||
| 1341 | ctl_stats->mapped = ctl_sarena->astats->astats.mapped; | ||
| 1342 | ctl_stats->retained = ctl_sarena->astats->astats | ||
| 1343 | .pa_shard_stats.pac_stats.retained; | ||
| 1344 | |||
| 1345 | ctl_background_thread_stats_read(tsdn); | ||
| 1346 | |||
| 1347 | #define READ_GLOBAL_MUTEX_PROF_DATA(i, mtx) \ | ||
| 1348 | malloc_mutex_lock(tsdn, &mtx); \ | ||
| 1349 | malloc_mutex_prof_read(tsdn, &ctl_stats->mutex_prof_data[i], &mtx); \ | ||
| 1350 | malloc_mutex_unlock(tsdn, &mtx); | ||
| 1351 | |||
| 1352 | if (config_prof && opt_prof) { | ||
| 1353 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1354 | global_prof_mutex_prof, bt2gctx_mtx); | ||
| 1355 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1356 | global_prof_mutex_prof_thds_data, tdatas_mtx); | ||
| 1357 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1358 | global_prof_mutex_prof_dump, prof_dump_mtx); | ||
| 1359 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1360 | global_prof_mutex_prof_recent_alloc, | ||
| 1361 | prof_recent_alloc_mtx); | ||
| 1362 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1363 | global_prof_mutex_prof_recent_dump, | ||
| 1364 | prof_recent_dump_mtx); | ||
| 1365 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1366 | global_prof_mutex_prof_stats, prof_stats_mtx); | ||
| 1367 | } | ||
| 1368 | if (have_background_thread) { | ||
| 1369 | READ_GLOBAL_MUTEX_PROF_DATA( | ||
| 1370 | global_prof_mutex_background_thread, | ||
| 1371 | background_thread_lock); | ||
| 1372 | } else { | ||
| 1373 | memset(&ctl_stats->mutex_prof_data[ | ||
| 1374 | global_prof_mutex_background_thread], 0, | ||
| 1375 | sizeof(mutex_prof_data_t)); | ||
| 1376 | } | ||
| 1377 | /* We own ctl mutex already. */ | ||
| 1378 | malloc_mutex_prof_read(tsdn, | ||
| 1379 | &ctl_stats->mutex_prof_data[global_prof_mutex_ctl], | ||
| 1380 | &ctl_mtx); | ||
| 1381 | #undef READ_GLOBAL_MUTEX_PROF_DATA | ||
| 1382 | } | ||
| 1383 | ctl_arenas->epoch++; | ||
| 1384 | } | ||
| 1385 | |||
| 1386 | static bool | ||
| 1387 | ctl_init(tsd_t *tsd) { | ||
| 1388 | bool ret; | ||
| 1389 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 1390 | |||
| 1391 | malloc_mutex_lock(tsdn, &ctl_mtx); | ||
| 1392 | if (!ctl_initialized) { | ||
| 1393 | ctl_arena_t *ctl_sarena, *ctl_darena; | ||
| 1394 | unsigned i; | ||
| 1395 | |||
| 1396 | /* | ||
| 1397 | * Allocate demand-zeroed space for pointers to the full | ||
| 1398 | * range of supported arena indices. | ||
| 1399 | */ | ||
| 1400 | if (ctl_arenas == NULL) { | ||
| 1401 | ctl_arenas = (ctl_arenas_t *)base_alloc(tsdn, | ||
| 1402 | b0get(), sizeof(ctl_arenas_t), QUANTUM); | ||
| 1403 | if (ctl_arenas == NULL) { | ||
| 1404 | ret = true; | ||
| 1405 | goto label_return; | ||
| 1406 | } | ||
| 1407 | } | ||
| 1408 | |||
| 1409 | if (config_stats && ctl_stats == NULL) { | ||
| 1410 | ctl_stats = (ctl_stats_t *)base_alloc(tsdn, b0get(), | ||
| 1411 | sizeof(ctl_stats_t), QUANTUM); | ||
| 1412 | if (ctl_stats == NULL) { | ||
| 1413 | ret = true; | ||
| 1414 | goto label_return; | ||
| 1415 | } | ||
| 1416 | } | ||
| 1417 | |||
| 1418 | /* | ||
| 1419 | * Allocate space for the current full range of arenas | ||
| 1420 | * here rather than doing it lazily elsewhere, in order | ||
| 1421 | * to limit when OOM-caused errors can occur. | ||
| 1422 | */ | ||
| 1423 | if ((ctl_sarena = arenas_i_impl(tsd, MALLCTL_ARENAS_ALL, false, | ||
| 1424 | true)) == NULL) { | ||
| 1425 | ret = true; | ||
| 1426 | goto label_return; | ||
| 1427 | } | ||
| 1428 | ctl_sarena->initialized = true; | ||
| 1429 | |||
| 1430 | if ((ctl_darena = arenas_i_impl(tsd, MALLCTL_ARENAS_DESTROYED, | ||
| 1431 | false, true)) == NULL) { | ||
| 1432 | ret = true; | ||
| 1433 | goto label_return; | ||
| 1434 | } | ||
| 1435 | ctl_arena_clear(ctl_darena); | ||
| 1436 | /* | ||
| 1437 | * Don't toggle ctl_darena to initialized until an arena is | ||
| 1438 | * actually destroyed, so that arena.<i>.initialized can be used | ||
| 1439 | * to query whether the stats are relevant. | ||
| 1440 | */ | ||
| 1441 | |||
| 1442 | ctl_arenas->narenas = narenas_total_get(); | ||
| 1443 | for (i = 0; i < ctl_arenas->narenas; i++) { | ||
| 1444 | if (arenas_i_impl(tsd, i, false, true) == NULL) { | ||
| 1445 | ret = true; | ||
| 1446 | goto label_return; | ||
| 1447 | } | ||
| 1448 | } | ||
| 1449 | |||
| 1450 | ql_new(&ctl_arenas->destroyed); | ||
| 1451 | ctl_refresh(tsdn); | ||
| 1452 | |||
| 1453 | ctl_initialized = true; | ||
| 1454 | } | ||
| 1455 | |||
| 1456 | ret = false; | ||
| 1457 | label_return: | ||
| 1458 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 1459 | return ret; | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | static int | ||
| 1463 | ctl_lookup(tsdn_t *tsdn, const ctl_named_node_t *starting_node, | ||
| 1464 | const char *name, const ctl_named_node_t **ending_nodep, size_t *mibp, | ||
| 1465 | size_t *depthp) { | ||
| 1466 | int ret; | ||
| 1467 | const char *elm, *tdot, *dot; | ||
| 1468 | size_t elen, i, j; | ||
| 1469 | const ctl_named_node_t *node; | ||
| 1470 | |||
| 1471 | elm = name; | ||
| 1472 | /* Equivalent to strchrnul(). */ | ||
| 1473 | dot = ((tdot = strchr(elm, '.')) != NULL) ? tdot : strchr(elm, '\0'); | ||
| 1474 | elen = (size_t)((uintptr_t)dot - (uintptr_t)elm); | ||
| 1475 | if (elen == 0) { | ||
| 1476 | ret = ENOENT; | ||
| 1477 | goto label_return; | ||
| 1478 | } | ||
| 1479 | node = starting_node; | ||
| 1480 | for (i = 0; i < *depthp; i++) { | ||
| 1481 | assert(node); | ||
| 1482 | assert(node->nchildren > 0); | ||
| 1483 | if (ctl_named_node(node->children) != NULL) { | ||
| 1484 | const ctl_named_node_t *pnode = node; | ||
| 1485 | |||
| 1486 | /* Children are named. */ | ||
| 1487 | for (j = 0; j < node->nchildren; j++) { | ||
| 1488 | const ctl_named_node_t *child = | ||
| 1489 | ctl_named_children(node, j); | ||
| 1490 | if (strlen(child->name) == elen && | ||
| 1491 | strncmp(elm, child->name, elen) == 0) { | ||
| 1492 | node = child; | ||
| 1493 | mibp[i] = j; | ||
| 1494 | break; | ||
| 1495 | } | ||
| 1496 | } | ||
| 1497 | if (node == pnode) { | ||
| 1498 | ret = ENOENT; | ||
| 1499 | goto label_return; | ||
| 1500 | } | ||
| 1501 | } else { | ||
| 1502 | uintmax_t index; | ||
| 1503 | const ctl_indexed_node_t *inode; | ||
| 1504 | |||
| 1505 | /* Children are indexed. */ | ||
| 1506 | index = malloc_strtoumax(elm, NULL, 10); | ||
| 1507 | if (index == UINTMAX_MAX || index > SIZE_T_MAX) { | ||
| 1508 | ret = ENOENT; | ||
| 1509 | goto label_return; | ||
| 1510 | } | ||
| 1511 | |||
| 1512 | inode = ctl_indexed_node(node->children); | ||
| 1513 | node = inode->index(tsdn, mibp, *depthp, (size_t)index); | ||
| 1514 | if (node == NULL) { | ||
| 1515 | ret = ENOENT; | ||
| 1516 | goto label_return; | ||
| 1517 | } | ||
| 1518 | |||
| 1519 | mibp[i] = (size_t)index; | ||
| 1520 | } | ||
| 1521 | |||
| 1522 | /* Reached the end? */ | ||
| 1523 | if (node->ctl != NULL || *dot == '\0') { | ||
| 1524 | /* Terminal node. */ | ||
| 1525 | if (*dot != '\0') { | ||
| 1526 | /* | ||
| 1527 | * The name contains more elements than are | ||
| 1528 | * in this path through the tree. | ||
| 1529 | */ | ||
| 1530 | ret = ENOENT; | ||
| 1531 | goto label_return; | ||
| 1532 | } | ||
| 1533 | /* Complete lookup successful. */ | ||
| 1534 | *depthp = i + 1; | ||
| 1535 | break; | ||
| 1536 | } | ||
| 1537 | |||
| 1538 | /* Update elm. */ | ||
| 1539 | elm = &dot[1]; | ||
| 1540 | dot = ((tdot = strchr(elm, '.')) != NULL) ? tdot : | ||
| 1541 | strchr(elm, '\0'); | ||
| 1542 | elen = (size_t)((uintptr_t)dot - (uintptr_t)elm); | ||
| 1543 | } | ||
| 1544 | if (ending_nodep != NULL) { | ||
| 1545 | *ending_nodep = node; | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | ret = 0; | ||
| 1549 | label_return: | ||
| 1550 | return ret; | ||
| 1551 | } | ||
| 1552 | |||
| 1553 | int | ||
| 1554 | ctl_byname(tsd_t *tsd, const char *name, void *oldp, size_t *oldlenp, | ||
| 1555 | void *newp, size_t newlen) { | ||
| 1556 | int ret; | ||
| 1557 | size_t depth; | ||
| 1558 | size_t mib[CTL_MAX_DEPTH]; | ||
| 1559 | const ctl_named_node_t *node; | ||
| 1560 | |||
| 1561 | if (!ctl_initialized && ctl_init(tsd)) { | ||
| 1562 | ret = EAGAIN; | ||
| 1563 | goto label_return; | ||
| 1564 | } | ||
| 1565 | |||
| 1566 | depth = CTL_MAX_DEPTH; | ||
| 1567 | ret = ctl_lookup(tsd_tsdn(tsd), super_root_node, name, &node, mib, | ||
| 1568 | &depth); | ||
| 1569 | if (ret != 0) { | ||
| 1570 | goto label_return; | ||
| 1571 | } | ||
| 1572 | |||
| 1573 | if (node != NULL && node->ctl) { | ||
| 1574 | ret = node->ctl(tsd, mib, depth, oldp, oldlenp, newp, newlen); | ||
| 1575 | } else { | ||
| 1576 | /* The name refers to a partial path through the ctl tree. */ | ||
| 1577 | ret = ENOENT; | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | label_return: | ||
| 1581 | return(ret); | ||
| 1582 | } | ||
| 1583 | |||
| 1584 | int | ||
| 1585 | ctl_nametomib(tsd_t *tsd, const char *name, size_t *mibp, size_t *miblenp) { | ||
| 1586 | int ret; | ||
| 1587 | |||
| 1588 | if (!ctl_initialized && ctl_init(tsd)) { | ||
| 1589 | ret = EAGAIN; | ||
| 1590 | goto label_return; | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | ret = ctl_lookup(tsd_tsdn(tsd), super_root_node, name, NULL, mibp, | ||
| 1594 | miblenp); | ||
| 1595 | label_return: | ||
| 1596 | return(ret); | ||
| 1597 | } | ||
| 1598 | |||
| 1599 | static int | ||
| 1600 | ctl_lookupbymib(tsdn_t *tsdn, const ctl_named_node_t **ending_nodep, | ||
| 1601 | const size_t *mib, size_t miblen) { | ||
| 1602 | int ret; | ||
| 1603 | |||
| 1604 | const ctl_named_node_t *node = super_root_node; | ||
| 1605 | for (size_t i = 0; i < miblen; i++) { | ||
| 1606 | assert(node); | ||
| 1607 | assert(node->nchildren > 0); | ||
| 1608 | if (ctl_named_node(node->children) != NULL) { | ||
| 1609 | /* Children are named. */ | ||
| 1610 | if (node->nchildren <= mib[i]) { | ||
| 1611 | ret = ENOENT; | ||
| 1612 | goto label_return; | ||
| 1613 | } | ||
| 1614 | node = ctl_named_children(node, mib[i]); | ||
| 1615 | } else { | ||
| 1616 | const ctl_indexed_node_t *inode; | ||
| 1617 | |||
| 1618 | /* Indexed element. */ | ||
| 1619 | inode = ctl_indexed_node(node->children); | ||
| 1620 | node = inode->index(tsdn, mib, miblen, mib[i]); | ||
| 1621 | if (node == NULL) { | ||
| 1622 | ret = ENOENT; | ||
| 1623 | goto label_return; | ||
| 1624 | } | ||
| 1625 | } | ||
| 1626 | } | ||
| 1627 | assert(ending_nodep != NULL); | ||
| 1628 | *ending_nodep = node; | ||
| 1629 | ret = 0; | ||
| 1630 | |||
| 1631 | label_return: | ||
| 1632 | return(ret); | ||
| 1633 | } | ||
| 1634 | |||
| 1635 | int | ||
| 1636 | ctl_bymib(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 1637 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 1638 | int ret; | ||
| 1639 | const ctl_named_node_t *node; | ||
| 1640 | |||
| 1641 | if (!ctl_initialized && ctl_init(tsd)) { | ||
| 1642 | ret = EAGAIN; | ||
| 1643 | goto label_return; | ||
| 1644 | } | ||
| 1645 | |||
| 1646 | ret = ctl_lookupbymib(tsd_tsdn(tsd), &node, mib, miblen); | ||
| 1647 | if (ret != 0) { | ||
| 1648 | goto label_return; | ||
| 1649 | } | ||
| 1650 | |||
| 1651 | /* Call the ctl function. */ | ||
| 1652 | if (node && node->ctl) { | ||
| 1653 | ret = node->ctl(tsd, mib, miblen, oldp, oldlenp, newp, newlen); | ||
| 1654 | } else { | ||
| 1655 | /* Partial MIB. */ | ||
| 1656 | ret = ENOENT; | ||
| 1657 | } | ||
| 1658 | |||
| 1659 | label_return: | ||
| 1660 | return(ret); | ||
| 1661 | } | ||
| 1662 | |||
| 1663 | int | ||
| 1664 | ctl_mibnametomib(tsd_t *tsd, size_t *mib, size_t miblen, const char *name, | ||
| 1665 | size_t *miblenp) { | ||
| 1666 | int ret; | ||
| 1667 | const ctl_named_node_t *node; | ||
| 1668 | |||
| 1669 | if (!ctl_initialized && ctl_init(tsd)) { | ||
| 1670 | ret = EAGAIN; | ||
| 1671 | goto label_return; | ||
| 1672 | } | ||
| 1673 | |||
| 1674 | ret = ctl_lookupbymib(tsd_tsdn(tsd), &node, mib, miblen); | ||
| 1675 | if (ret != 0) { | ||
| 1676 | goto label_return; | ||
| 1677 | } | ||
| 1678 | if (node == NULL || node->ctl != NULL) { | ||
| 1679 | ret = ENOENT; | ||
| 1680 | goto label_return; | ||
| 1681 | } | ||
| 1682 | |||
| 1683 | assert(miblenp != NULL); | ||
| 1684 | assert(*miblenp >= miblen); | ||
| 1685 | *miblenp -= miblen; | ||
| 1686 | ret = ctl_lookup(tsd_tsdn(tsd), node, name, NULL, mib + miblen, | ||
| 1687 | miblenp); | ||
| 1688 | *miblenp += miblen; | ||
| 1689 | label_return: | ||
| 1690 | return(ret); | ||
| 1691 | } | ||
| 1692 | |||
| 1693 | int | ||
| 1694 | ctl_bymibname(tsd_t *tsd, size_t *mib, size_t miblen, const char *name, | ||
| 1695 | size_t *miblenp, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 1696 | int ret; | ||
| 1697 | const ctl_named_node_t *node; | ||
| 1698 | |||
| 1699 | if (!ctl_initialized && ctl_init(tsd)) { | ||
| 1700 | ret = EAGAIN; | ||
| 1701 | goto label_return; | ||
| 1702 | } | ||
| 1703 | |||
| 1704 | ret = ctl_lookupbymib(tsd_tsdn(tsd), &node, mib, miblen); | ||
| 1705 | if (ret != 0) { | ||
| 1706 | goto label_return; | ||
| 1707 | } | ||
| 1708 | if (node == NULL || node->ctl != NULL) { | ||
| 1709 | ret = ENOENT; | ||
| 1710 | goto label_return; | ||
| 1711 | } | ||
| 1712 | |||
| 1713 | assert(miblenp != NULL); | ||
| 1714 | assert(*miblenp >= miblen); | ||
| 1715 | *miblenp -= miblen; | ||
| 1716 | /* | ||
| 1717 | * The same node supplies the starting node and stores the ending node. | ||
| 1718 | */ | ||
| 1719 | ret = ctl_lookup(tsd_tsdn(tsd), node, name, &node, mib + miblen, | ||
| 1720 | miblenp); | ||
| 1721 | *miblenp += miblen; | ||
| 1722 | if (ret != 0) { | ||
| 1723 | goto label_return; | ||
| 1724 | } | ||
| 1725 | |||
| 1726 | if (node != NULL && node->ctl) { | ||
| 1727 | ret = node->ctl(tsd, mib, *miblenp, oldp, oldlenp, newp, | ||
| 1728 | newlen); | ||
| 1729 | } else { | ||
| 1730 | /* The name refers to a partial path through the ctl tree. */ | ||
| 1731 | ret = ENOENT; | ||
| 1732 | } | ||
| 1733 | |||
| 1734 | label_return: | ||
| 1735 | return(ret); | ||
| 1736 | } | ||
| 1737 | |||
| 1738 | bool | ||
| 1739 | ctl_boot(void) { | ||
| 1740 | if (malloc_mutex_init(&ctl_mtx, "ctl", WITNESS_RANK_CTL, | ||
| 1741 | malloc_mutex_rank_exclusive)) { | ||
| 1742 | return true; | ||
| 1743 | } | ||
| 1744 | |||
| 1745 | ctl_initialized = false; | ||
| 1746 | |||
| 1747 | return false; | ||
| 1748 | } | ||
| 1749 | |||
| 1750 | void | ||
| 1751 | ctl_prefork(tsdn_t *tsdn) { | ||
| 1752 | malloc_mutex_prefork(tsdn, &ctl_mtx); | ||
| 1753 | } | ||
| 1754 | |||
| 1755 | void | ||
| 1756 | ctl_postfork_parent(tsdn_t *tsdn) { | ||
| 1757 | malloc_mutex_postfork_parent(tsdn, &ctl_mtx); | ||
| 1758 | } | ||
| 1759 | |||
| 1760 | void | ||
| 1761 | ctl_postfork_child(tsdn_t *tsdn) { | ||
| 1762 | malloc_mutex_postfork_child(tsdn, &ctl_mtx); | ||
| 1763 | } | ||
| 1764 | |||
| 1765 | void | ||
| 1766 | ctl_mtx_assert_held(tsdn_t *tsdn) { | ||
| 1767 | malloc_mutex_assert_owner(tsdn, &ctl_mtx); | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | /******************************************************************************/ | ||
| 1771 | /* *_ctl() functions. */ | ||
| 1772 | |||
| 1773 | #define READONLY() do { \ | ||
| 1774 | if (newp != NULL || newlen != 0) { \ | ||
| 1775 | ret = EPERM; \ | ||
| 1776 | goto label_return; \ | ||
| 1777 | } \ | ||
| 1778 | } while (0) | ||
| 1779 | |||
| 1780 | #define WRITEONLY() do { \ | ||
| 1781 | if (oldp != NULL || oldlenp != NULL) { \ | ||
| 1782 | ret = EPERM; \ | ||
| 1783 | goto label_return; \ | ||
| 1784 | } \ | ||
| 1785 | } while (0) | ||
| 1786 | |||
| 1787 | /* Can read or write, but not both. */ | ||
| 1788 | #define READ_XOR_WRITE() do { \ | ||
| 1789 | if ((oldp != NULL && oldlenp != NULL) && (newp != NULL || \ | ||
| 1790 | newlen != 0)) { \ | ||
| 1791 | ret = EPERM; \ | ||
| 1792 | goto label_return; \ | ||
| 1793 | } \ | ||
| 1794 | } while (0) | ||
| 1795 | |||
| 1796 | /* Can neither read nor write. */ | ||
| 1797 | #define NEITHER_READ_NOR_WRITE() do { \ | ||
| 1798 | if (oldp != NULL || oldlenp != NULL || newp != NULL || \ | ||
| 1799 | newlen != 0) { \ | ||
| 1800 | ret = EPERM; \ | ||
| 1801 | goto label_return; \ | ||
| 1802 | } \ | ||
| 1803 | } while (0) | ||
| 1804 | |||
| 1805 | /* Verify that the space provided is enough. */ | ||
| 1806 | #define VERIFY_READ(t) do { \ | ||
| 1807 | if (oldp == NULL || oldlenp == NULL || *oldlenp != sizeof(t)) { \ | ||
| 1808 | *oldlenp = 0; \ | ||
| 1809 | ret = EINVAL; \ | ||
| 1810 | goto label_return; \ | ||
| 1811 | } \ | ||
| 1812 | } while (0) | ||
| 1813 | |||
| 1814 | #define READ(v, t) do { \ | ||
| 1815 | if (oldp != NULL && oldlenp != NULL) { \ | ||
| 1816 | if (*oldlenp != sizeof(t)) { \ | ||
| 1817 | size_t copylen = (sizeof(t) <= *oldlenp) \ | ||
| 1818 | ? sizeof(t) : *oldlenp; \ | ||
| 1819 | memcpy(oldp, (void *)&(v), copylen); \ | ||
| 1820 | *oldlenp = copylen; \ | ||
| 1821 | ret = EINVAL; \ | ||
| 1822 | goto label_return; \ | ||
| 1823 | } \ | ||
| 1824 | *(t *)oldp = (v); \ | ||
| 1825 | } \ | ||
| 1826 | } while (0) | ||
| 1827 | |||
| 1828 | #define WRITE(v, t) do { \ | ||
| 1829 | if (newp != NULL) { \ | ||
| 1830 | if (newlen != sizeof(t)) { \ | ||
| 1831 | ret = EINVAL; \ | ||
| 1832 | goto label_return; \ | ||
| 1833 | } \ | ||
| 1834 | (v) = *(t *)newp; \ | ||
| 1835 | } \ | ||
| 1836 | } while (0) | ||
| 1837 | |||
| 1838 | #define ASSURED_WRITE(v, t) do { \ | ||
| 1839 | if (newp == NULL || newlen != sizeof(t)) { \ | ||
| 1840 | ret = EINVAL; \ | ||
| 1841 | goto label_return; \ | ||
| 1842 | } \ | ||
| 1843 | (v) = *(t *)newp; \ | ||
| 1844 | } while (0) | ||
| 1845 | |||
| 1846 | #define MIB_UNSIGNED(v, i) do { \ | ||
| 1847 | if (mib[i] > UINT_MAX) { \ | ||
| 1848 | ret = EFAULT; \ | ||
| 1849 | goto label_return; \ | ||
| 1850 | } \ | ||
| 1851 | v = (unsigned)mib[i]; \ | ||
| 1852 | } while (0) | ||
| 1853 | |||
| 1854 | /* | ||
| 1855 | * There's a lot of code duplication in the following macros due to limitations | ||
| 1856 | * in how nested cpp macros are expanded. | ||
| 1857 | */ | ||
| 1858 | #define CTL_RO_CLGEN(c, l, n, v, t) \ | ||
| 1859 | static int \ | ||
| 1860 | n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \ | ||
| 1861 | size_t *oldlenp, void *newp, size_t newlen) { \ | ||
| 1862 | int ret; \ | ||
| 1863 | t oldval; \ | ||
| 1864 | \ | ||
| 1865 | if (!(c)) { \ | ||
| 1866 | return ENOENT; \ | ||
| 1867 | } \ | ||
| 1868 | if (l) { \ | ||
| 1869 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ | ||
| 1870 | } \ | ||
| 1871 | READONLY(); \ | ||
| 1872 | oldval = (v); \ | ||
| 1873 | READ(oldval, t); \ | ||
| 1874 | \ | ||
| 1875 | ret = 0; \ | ||
| 1876 | label_return: \ | ||
| 1877 | if (l) { \ | ||
| 1878 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ | ||
| 1879 | } \ | ||
| 1880 | return ret; \ | ||
| 1881 | } | ||
| 1882 | |||
| 1883 | #define CTL_RO_CGEN(c, n, v, t) \ | ||
| 1884 | static int \ | ||
| 1885 | n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ | ||
| 1886 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ | ||
| 1887 | int ret; \ | ||
| 1888 | t oldval; \ | ||
| 1889 | \ | ||
| 1890 | if (!(c)) { \ | ||
| 1891 | return ENOENT; \ | ||
| 1892 | } \ | ||
| 1893 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ | ||
| 1894 | READONLY(); \ | ||
| 1895 | oldval = (v); \ | ||
| 1896 | READ(oldval, t); \ | ||
| 1897 | \ | ||
| 1898 | ret = 0; \ | ||
| 1899 | label_return: \ | ||
| 1900 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ | ||
| 1901 | return ret; \ | ||
| 1902 | } | ||
| 1903 | |||
| 1904 | #define CTL_RO_GEN(n, v, t) \ | ||
| 1905 | static int \ | ||
| 1906 | n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \ | ||
| 1907 | size_t *oldlenp, void *newp, size_t newlen) { \ | ||
| 1908 | int ret; \ | ||
| 1909 | t oldval; \ | ||
| 1910 | \ | ||
| 1911 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \ | ||
| 1912 | READONLY(); \ | ||
| 1913 | oldval = (v); \ | ||
| 1914 | READ(oldval, t); \ | ||
| 1915 | \ | ||
| 1916 | ret = 0; \ | ||
| 1917 | label_return: \ | ||
| 1918 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \ | ||
| 1919 | return ret; \ | ||
| 1920 | } | ||
| 1921 | |||
| 1922 | /* | ||
| 1923 | * ctl_mtx is not acquired, under the assumption that no pertinent data will | ||
| 1924 | * mutate during the call. | ||
| 1925 | */ | ||
| 1926 | #define CTL_RO_NL_CGEN(c, n, v, t) \ | ||
| 1927 | static int \ | ||
| 1928 | n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ | ||
| 1929 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ | ||
| 1930 | int ret; \ | ||
| 1931 | t oldval; \ | ||
| 1932 | \ | ||
| 1933 | if (!(c)) { \ | ||
| 1934 | return ENOENT; \ | ||
| 1935 | } \ | ||
| 1936 | READONLY(); \ | ||
| 1937 | oldval = (v); \ | ||
| 1938 | READ(oldval, t); \ | ||
| 1939 | \ | ||
| 1940 | ret = 0; \ | ||
| 1941 | label_return: \ | ||
| 1942 | return ret; \ | ||
| 1943 | } | ||
| 1944 | |||
| 1945 | #define CTL_RO_NL_GEN(n, v, t) \ | ||
| 1946 | static int \ | ||
| 1947 | n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ | ||
| 1948 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ | ||
| 1949 | int ret; \ | ||
| 1950 | t oldval; \ | ||
| 1951 | \ | ||
| 1952 | READONLY(); \ | ||
| 1953 | oldval = (v); \ | ||
| 1954 | READ(oldval, t); \ | ||
| 1955 | \ | ||
| 1956 | ret = 0; \ | ||
| 1957 | label_return: \ | ||
| 1958 | return ret; \ | ||
| 1959 | } | ||
| 1960 | |||
| 1961 | #define CTL_RO_CONFIG_GEN(n, t) \ | ||
| 1962 | static int \ | ||
| 1963 | n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \ | ||
| 1964 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { \ | ||
| 1965 | int ret; \ | ||
| 1966 | t oldval; \ | ||
| 1967 | \ | ||
| 1968 | READONLY(); \ | ||
| 1969 | oldval = n; \ | ||
| 1970 | READ(oldval, t); \ | ||
| 1971 | \ | ||
| 1972 | ret = 0; \ | ||
| 1973 | label_return: \ | ||
| 1974 | return ret; \ | ||
| 1975 | } | ||
| 1976 | |||
| 1977 | /******************************************************************************/ | ||
| 1978 | |||
| 1979 | CTL_RO_NL_GEN(version, JEMALLOC_VERSION, const char *) | ||
| 1980 | |||
| 1981 | static int | ||
| 1982 | epoch_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 1983 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 1984 | int ret; | ||
| 1985 | UNUSED uint64_t newval; | ||
| 1986 | |||
| 1987 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 1988 | WRITE(newval, uint64_t); | ||
| 1989 | if (newp != NULL) { | ||
| 1990 | ctl_refresh(tsd_tsdn(tsd)); | ||
| 1991 | } | ||
| 1992 | READ(ctl_arenas->epoch, uint64_t); | ||
| 1993 | |||
| 1994 | ret = 0; | ||
| 1995 | label_return: | ||
| 1996 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 1997 | return ret; | ||
| 1998 | } | ||
| 1999 | |||
| 2000 | static int | ||
| 2001 | background_thread_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2002 | size_t miblen, void *oldp, size_t *oldlenp, | ||
| 2003 | void *newp, size_t newlen) { | ||
| 2004 | int ret; | ||
| 2005 | bool oldval; | ||
| 2006 | |||
| 2007 | if (!have_background_thread) { | ||
| 2008 | return ENOENT; | ||
| 2009 | } | ||
| 2010 | background_thread_ctl_init(tsd_tsdn(tsd)); | ||
| 2011 | |||
| 2012 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2013 | malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 2014 | if (newp == NULL) { | ||
| 2015 | oldval = background_thread_enabled(); | ||
| 2016 | READ(oldval, bool); | ||
| 2017 | } else { | ||
| 2018 | if (newlen != sizeof(bool)) { | ||
| 2019 | ret = EINVAL; | ||
| 2020 | goto label_return; | ||
| 2021 | } | ||
| 2022 | oldval = background_thread_enabled(); | ||
| 2023 | READ(oldval, bool); | ||
| 2024 | |||
| 2025 | bool newval = *(bool *)newp; | ||
| 2026 | if (newval == oldval) { | ||
| 2027 | ret = 0; | ||
| 2028 | goto label_return; | ||
| 2029 | } | ||
| 2030 | |||
| 2031 | background_thread_enabled_set(tsd_tsdn(tsd), newval); | ||
| 2032 | if (newval) { | ||
| 2033 | if (background_threads_enable(tsd)) { | ||
| 2034 | ret = EFAULT; | ||
| 2035 | goto label_return; | ||
| 2036 | } | ||
| 2037 | } else { | ||
| 2038 | if (background_threads_disable(tsd)) { | ||
| 2039 | ret = EFAULT; | ||
| 2040 | goto label_return; | ||
| 2041 | } | ||
| 2042 | } | ||
| 2043 | } | ||
| 2044 | ret = 0; | ||
| 2045 | label_return: | ||
| 2046 | malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 2047 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2048 | |||
| 2049 | return ret; | ||
| 2050 | } | ||
| 2051 | |||
| 2052 | static int | ||
| 2053 | max_background_threads_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2054 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2055 | size_t newlen) { | ||
| 2056 | int ret; | ||
| 2057 | size_t oldval; | ||
| 2058 | |||
| 2059 | if (!have_background_thread) { | ||
| 2060 | return ENOENT; | ||
| 2061 | } | ||
| 2062 | background_thread_ctl_init(tsd_tsdn(tsd)); | ||
| 2063 | |||
| 2064 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2065 | malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 2066 | if (newp == NULL) { | ||
| 2067 | oldval = max_background_threads; | ||
| 2068 | READ(oldval, size_t); | ||
| 2069 | } else { | ||
| 2070 | if (newlen != sizeof(size_t)) { | ||
| 2071 | ret = EINVAL; | ||
| 2072 | goto label_return; | ||
| 2073 | } | ||
| 2074 | oldval = max_background_threads; | ||
| 2075 | READ(oldval, size_t); | ||
| 2076 | |||
| 2077 | size_t newval = *(size_t *)newp; | ||
| 2078 | if (newval == oldval) { | ||
| 2079 | ret = 0; | ||
| 2080 | goto label_return; | ||
| 2081 | } | ||
| 2082 | if (newval > opt_max_background_threads) { | ||
| 2083 | ret = EINVAL; | ||
| 2084 | goto label_return; | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | if (background_thread_enabled()) { | ||
| 2088 | background_thread_enabled_set(tsd_tsdn(tsd), false); | ||
| 2089 | if (background_threads_disable(tsd)) { | ||
| 2090 | ret = EFAULT; | ||
| 2091 | goto label_return; | ||
| 2092 | } | ||
| 2093 | max_background_threads = newval; | ||
| 2094 | background_thread_enabled_set(tsd_tsdn(tsd), true); | ||
| 2095 | if (background_threads_enable(tsd)) { | ||
| 2096 | ret = EFAULT; | ||
| 2097 | goto label_return; | ||
| 2098 | } | ||
| 2099 | } else { | ||
| 2100 | max_background_threads = newval; | ||
| 2101 | } | ||
| 2102 | } | ||
| 2103 | ret = 0; | ||
| 2104 | label_return: | ||
| 2105 | malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 2106 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2107 | |||
| 2108 | return ret; | ||
| 2109 | } | ||
| 2110 | |||
| 2111 | /******************************************************************************/ | ||
| 2112 | |||
| 2113 | CTL_RO_CONFIG_GEN(config_cache_oblivious, bool) | ||
| 2114 | CTL_RO_CONFIG_GEN(config_debug, bool) | ||
| 2115 | CTL_RO_CONFIG_GEN(config_fill, bool) | ||
| 2116 | CTL_RO_CONFIG_GEN(config_lazy_lock, bool) | ||
| 2117 | CTL_RO_CONFIG_GEN(config_malloc_conf, const char *) | ||
| 2118 | CTL_RO_CONFIG_GEN(config_opt_safety_checks, bool) | ||
| 2119 | CTL_RO_CONFIG_GEN(config_prof, bool) | ||
| 2120 | CTL_RO_CONFIG_GEN(config_prof_libgcc, bool) | ||
| 2121 | CTL_RO_CONFIG_GEN(config_prof_libunwind, bool) | ||
| 2122 | CTL_RO_CONFIG_GEN(config_stats, bool) | ||
| 2123 | CTL_RO_CONFIG_GEN(config_utrace, bool) | ||
| 2124 | CTL_RO_CONFIG_GEN(config_xmalloc, bool) | ||
| 2125 | |||
| 2126 | /******************************************************************************/ | ||
| 2127 | |||
| 2128 | CTL_RO_NL_GEN(opt_abort, opt_abort, bool) | ||
| 2129 | CTL_RO_NL_GEN(opt_abort_conf, opt_abort_conf, bool) | ||
| 2130 | CTL_RO_NL_GEN(opt_cache_oblivious, opt_cache_oblivious, bool) | ||
| 2131 | CTL_RO_NL_GEN(opt_trust_madvise, opt_trust_madvise, bool) | ||
| 2132 | CTL_RO_NL_GEN(opt_confirm_conf, opt_confirm_conf, bool) | ||
| 2133 | |||
| 2134 | /* HPA options. */ | ||
| 2135 | CTL_RO_NL_GEN(opt_hpa, opt_hpa, bool) | ||
| 2136 | CTL_RO_NL_GEN(opt_hpa_hugification_threshold, | ||
| 2137 | opt_hpa_opts.hugification_threshold, size_t) | ||
| 2138 | CTL_RO_NL_GEN(opt_hpa_hugify_delay_ms, opt_hpa_opts.hugify_delay_ms, uint64_t) | ||
| 2139 | CTL_RO_NL_GEN(opt_hpa_min_purge_interval_ms, opt_hpa_opts.min_purge_interval_ms, | ||
| 2140 | uint64_t) | ||
| 2141 | |||
| 2142 | /* | ||
| 2143 | * This will have to change before we publicly document this option; fxp_t and | ||
| 2144 | * its representation are internal implementation details. | ||
| 2145 | */ | ||
| 2146 | CTL_RO_NL_GEN(opt_hpa_dirty_mult, opt_hpa_opts.dirty_mult, fxp_t) | ||
| 2147 | CTL_RO_NL_GEN(opt_hpa_slab_max_alloc, opt_hpa_opts.slab_max_alloc, size_t) | ||
| 2148 | |||
| 2149 | /* HPA SEC options */ | ||
| 2150 | CTL_RO_NL_GEN(opt_hpa_sec_nshards, opt_hpa_sec_opts.nshards, size_t) | ||
| 2151 | CTL_RO_NL_GEN(opt_hpa_sec_max_alloc, opt_hpa_sec_opts.max_alloc, size_t) | ||
| 2152 | CTL_RO_NL_GEN(opt_hpa_sec_max_bytes, opt_hpa_sec_opts.max_bytes, size_t) | ||
| 2153 | CTL_RO_NL_GEN(opt_hpa_sec_bytes_after_flush, opt_hpa_sec_opts.bytes_after_flush, | ||
| 2154 | size_t) | ||
| 2155 | CTL_RO_NL_GEN(opt_hpa_sec_batch_fill_extra, opt_hpa_sec_opts.batch_fill_extra, | ||
| 2156 | size_t) | ||
| 2157 | |||
| 2158 | CTL_RO_NL_GEN(opt_metadata_thp, metadata_thp_mode_names[opt_metadata_thp], | ||
| 2159 | const char *) | ||
| 2160 | CTL_RO_NL_GEN(opt_retain, opt_retain, bool) | ||
| 2161 | CTL_RO_NL_GEN(opt_dss, opt_dss, const char *) | ||
| 2162 | CTL_RO_NL_GEN(opt_narenas, opt_narenas, unsigned) | ||
| 2163 | CTL_RO_NL_GEN(opt_percpu_arena, percpu_arena_mode_names[opt_percpu_arena], | ||
| 2164 | const char *) | ||
| 2165 | CTL_RO_NL_GEN(opt_mutex_max_spin, opt_mutex_max_spin, int64_t) | ||
| 2166 | CTL_RO_NL_GEN(opt_oversize_threshold, opt_oversize_threshold, size_t) | ||
| 2167 | CTL_RO_NL_GEN(opt_background_thread, opt_background_thread, bool) | ||
| 2168 | CTL_RO_NL_GEN(opt_max_background_threads, opt_max_background_threads, size_t) | ||
| 2169 | CTL_RO_NL_GEN(opt_dirty_decay_ms, opt_dirty_decay_ms, ssize_t) | ||
| 2170 | CTL_RO_NL_GEN(opt_muzzy_decay_ms, opt_muzzy_decay_ms, ssize_t) | ||
| 2171 | CTL_RO_NL_GEN(opt_stats_print, opt_stats_print, bool) | ||
| 2172 | CTL_RO_NL_GEN(opt_stats_print_opts, opt_stats_print_opts, const char *) | ||
| 2173 | CTL_RO_NL_GEN(opt_stats_interval, opt_stats_interval, int64_t) | ||
| 2174 | CTL_RO_NL_GEN(opt_stats_interval_opts, opt_stats_interval_opts, const char *) | ||
| 2175 | CTL_RO_NL_CGEN(config_fill, opt_junk, opt_junk, const char *) | ||
| 2176 | CTL_RO_NL_CGEN(config_fill, opt_zero, opt_zero, bool) | ||
| 2177 | CTL_RO_NL_CGEN(config_utrace, opt_utrace, opt_utrace, bool) | ||
| 2178 | CTL_RO_NL_CGEN(config_xmalloc, opt_xmalloc, opt_xmalloc, bool) | ||
| 2179 | CTL_RO_NL_CGEN(config_enable_cxx, opt_experimental_infallible_new, | ||
| 2180 | opt_experimental_infallible_new, bool) | ||
| 2181 | CTL_RO_NL_GEN(opt_tcache, opt_tcache, bool) | ||
| 2182 | CTL_RO_NL_GEN(opt_tcache_max, opt_tcache_max, size_t) | ||
| 2183 | CTL_RO_NL_GEN(opt_tcache_nslots_small_min, opt_tcache_nslots_small_min, | ||
| 2184 | unsigned) | ||
| 2185 | CTL_RO_NL_GEN(opt_tcache_nslots_small_max, opt_tcache_nslots_small_max, | ||
| 2186 | unsigned) | ||
| 2187 | CTL_RO_NL_GEN(opt_tcache_nslots_large, opt_tcache_nslots_large, unsigned) | ||
| 2188 | CTL_RO_NL_GEN(opt_lg_tcache_nslots_mul, opt_lg_tcache_nslots_mul, ssize_t) | ||
| 2189 | CTL_RO_NL_GEN(opt_tcache_gc_incr_bytes, opt_tcache_gc_incr_bytes, size_t) | ||
| 2190 | CTL_RO_NL_GEN(opt_tcache_gc_delay_bytes, opt_tcache_gc_delay_bytes, size_t) | ||
| 2191 | CTL_RO_NL_GEN(opt_lg_tcache_flush_small_div, opt_lg_tcache_flush_small_div, | ||
| 2192 | unsigned) | ||
| 2193 | CTL_RO_NL_GEN(opt_lg_tcache_flush_large_div, opt_lg_tcache_flush_large_div, | ||
| 2194 | unsigned) | ||
| 2195 | CTL_RO_NL_GEN(opt_thp, thp_mode_names[opt_thp], const char *) | ||
| 2196 | CTL_RO_NL_GEN(opt_lg_extent_max_active_fit, opt_lg_extent_max_active_fit, | ||
| 2197 | size_t) | ||
| 2198 | CTL_RO_NL_CGEN(config_prof, opt_prof, opt_prof, bool) | ||
| 2199 | CTL_RO_NL_CGEN(config_prof, opt_prof_prefix, opt_prof_prefix, const char *) | ||
| 2200 | CTL_RO_NL_CGEN(config_prof, opt_prof_active, opt_prof_active, bool) | ||
| 2201 | CTL_RO_NL_CGEN(config_prof, opt_prof_thread_active_init, | ||
| 2202 | opt_prof_thread_active_init, bool) | ||
| 2203 | CTL_RO_NL_CGEN(config_prof, opt_lg_prof_sample, opt_lg_prof_sample, size_t) | ||
| 2204 | CTL_RO_NL_CGEN(config_prof, opt_prof_accum, opt_prof_accum, bool) | ||
| 2205 | CTL_RO_NL_CGEN(config_prof, opt_lg_prof_interval, opt_lg_prof_interval, ssize_t) | ||
| 2206 | CTL_RO_NL_CGEN(config_prof, opt_prof_gdump, opt_prof_gdump, bool) | ||
| 2207 | CTL_RO_NL_CGEN(config_prof, opt_prof_final, opt_prof_final, bool) | ||
| 2208 | CTL_RO_NL_CGEN(config_prof, opt_prof_leak, opt_prof_leak, bool) | ||
| 2209 | CTL_RO_NL_CGEN(config_prof, opt_prof_leak_error, opt_prof_leak_error, bool) | ||
| 2210 | CTL_RO_NL_CGEN(config_prof, opt_prof_recent_alloc_max, | ||
| 2211 | opt_prof_recent_alloc_max, ssize_t) | ||
| 2212 | CTL_RO_NL_CGEN(config_prof, opt_prof_stats, opt_prof_stats, bool) | ||
| 2213 | CTL_RO_NL_CGEN(config_prof, opt_prof_sys_thread_name, opt_prof_sys_thread_name, | ||
| 2214 | bool) | ||
| 2215 | CTL_RO_NL_CGEN(config_prof, opt_prof_time_res, | ||
| 2216 | prof_time_res_mode_names[opt_prof_time_res], const char *) | ||
| 2217 | CTL_RO_NL_CGEN(config_uaf_detection, opt_lg_san_uaf_align, | ||
| 2218 | opt_lg_san_uaf_align, ssize_t) | ||
| 2219 | CTL_RO_NL_GEN(opt_zero_realloc, | ||
| 2220 | zero_realloc_mode_names[opt_zero_realloc_action], const char *) | ||
| 2221 | |||
| 2222 | /******************************************************************************/ | ||
| 2223 | |||
| 2224 | static int | ||
| 2225 | thread_arena_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2226 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2227 | int ret; | ||
| 2228 | arena_t *oldarena; | ||
| 2229 | unsigned newind, oldind; | ||
| 2230 | |||
| 2231 | oldarena = arena_choose(tsd, NULL); | ||
| 2232 | if (oldarena == NULL) { | ||
| 2233 | return EAGAIN; | ||
| 2234 | } | ||
| 2235 | newind = oldind = arena_ind_get(oldarena); | ||
| 2236 | WRITE(newind, unsigned); | ||
| 2237 | READ(oldind, unsigned); | ||
| 2238 | |||
| 2239 | if (newind != oldind) { | ||
| 2240 | arena_t *newarena; | ||
| 2241 | |||
| 2242 | if (newind >= narenas_total_get()) { | ||
| 2243 | /* New arena index is out of range. */ | ||
| 2244 | ret = EFAULT; | ||
| 2245 | goto label_return; | ||
| 2246 | } | ||
| 2247 | |||
| 2248 | if (have_percpu_arena && | ||
| 2249 | PERCPU_ARENA_ENABLED(opt_percpu_arena)) { | ||
| 2250 | if (newind < percpu_arena_ind_limit(opt_percpu_arena)) { | ||
| 2251 | /* | ||
| 2252 | * If perCPU arena is enabled, thread_arena | ||
| 2253 | * control is not allowed for the auto arena | ||
| 2254 | * range. | ||
| 2255 | */ | ||
| 2256 | ret = EPERM; | ||
| 2257 | goto label_return; | ||
| 2258 | } | ||
| 2259 | } | ||
| 2260 | |||
| 2261 | /* Initialize arena if necessary. */ | ||
| 2262 | newarena = arena_get(tsd_tsdn(tsd), newind, true); | ||
| 2263 | if (newarena == NULL) { | ||
| 2264 | ret = EAGAIN; | ||
| 2265 | goto label_return; | ||
| 2266 | } | ||
| 2267 | /* Set new arena/tcache associations. */ | ||
| 2268 | arena_migrate(tsd, oldarena, newarena); | ||
| 2269 | if (tcache_available(tsd)) { | ||
| 2270 | tcache_arena_reassociate(tsd_tsdn(tsd), | ||
| 2271 | tsd_tcache_slowp_get(tsd), tsd_tcachep_get(tsd), | ||
| 2272 | newarena); | ||
| 2273 | } | ||
| 2274 | } | ||
| 2275 | |||
| 2276 | ret = 0; | ||
| 2277 | label_return: | ||
| 2278 | return ret; | ||
| 2279 | } | ||
| 2280 | |||
| 2281 | CTL_RO_NL_GEN(thread_allocated, tsd_thread_allocated_get(tsd), uint64_t) | ||
| 2282 | CTL_RO_NL_GEN(thread_allocatedp, tsd_thread_allocatedp_get(tsd), uint64_t *) | ||
| 2283 | CTL_RO_NL_GEN(thread_deallocated, tsd_thread_deallocated_get(tsd), uint64_t) | ||
| 2284 | CTL_RO_NL_GEN(thread_deallocatedp, tsd_thread_deallocatedp_get(tsd), uint64_t *) | ||
| 2285 | |||
| 2286 | static int | ||
| 2287 | thread_tcache_enabled_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2288 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2289 | size_t newlen) { | ||
| 2290 | int ret; | ||
| 2291 | bool oldval; | ||
| 2292 | |||
| 2293 | oldval = tcache_enabled_get(tsd); | ||
| 2294 | if (newp != NULL) { | ||
| 2295 | if (newlen != sizeof(bool)) { | ||
| 2296 | ret = EINVAL; | ||
| 2297 | goto label_return; | ||
| 2298 | } | ||
| 2299 | tcache_enabled_set(tsd, *(bool *)newp); | ||
| 2300 | } | ||
| 2301 | READ(oldval, bool); | ||
| 2302 | |||
| 2303 | ret = 0; | ||
| 2304 | label_return: | ||
| 2305 | return ret; | ||
| 2306 | } | ||
| 2307 | |||
| 2308 | static int | ||
| 2309 | thread_tcache_flush_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2310 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2311 | size_t newlen) { | ||
| 2312 | int ret; | ||
| 2313 | |||
| 2314 | if (!tcache_available(tsd)) { | ||
| 2315 | ret = EFAULT; | ||
| 2316 | goto label_return; | ||
| 2317 | } | ||
| 2318 | |||
| 2319 | NEITHER_READ_NOR_WRITE(); | ||
| 2320 | |||
| 2321 | tcache_flush(tsd); | ||
| 2322 | |||
| 2323 | ret = 0; | ||
| 2324 | label_return: | ||
| 2325 | return ret; | ||
| 2326 | } | ||
| 2327 | |||
| 2328 | static int | ||
| 2329 | thread_peak_read_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2330 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2331 | size_t newlen) { | ||
| 2332 | int ret; | ||
| 2333 | if (!config_stats) { | ||
| 2334 | return ENOENT; | ||
| 2335 | } | ||
| 2336 | READONLY(); | ||
| 2337 | peak_event_update(tsd); | ||
| 2338 | uint64_t result = peak_event_max(tsd); | ||
| 2339 | READ(result, uint64_t); | ||
| 2340 | ret = 0; | ||
| 2341 | label_return: | ||
| 2342 | return ret; | ||
| 2343 | } | ||
| 2344 | |||
| 2345 | static int | ||
| 2346 | thread_peak_reset_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2347 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2348 | size_t newlen) { | ||
| 2349 | int ret; | ||
| 2350 | if (!config_stats) { | ||
| 2351 | return ENOENT; | ||
| 2352 | } | ||
| 2353 | NEITHER_READ_NOR_WRITE(); | ||
| 2354 | peak_event_zero(tsd); | ||
| 2355 | ret = 0; | ||
| 2356 | label_return: | ||
| 2357 | return ret; | ||
| 2358 | } | ||
| 2359 | |||
| 2360 | static int | ||
| 2361 | thread_prof_name_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2362 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2363 | size_t newlen) { | ||
| 2364 | int ret; | ||
| 2365 | |||
| 2366 | if (!config_prof || !opt_prof) { | ||
| 2367 | return ENOENT; | ||
| 2368 | } | ||
| 2369 | |||
| 2370 | READ_XOR_WRITE(); | ||
| 2371 | |||
| 2372 | if (newp != NULL) { | ||
| 2373 | if (newlen != sizeof(const char *)) { | ||
| 2374 | ret = EINVAL; | ||
| 2375 | goto label_return; | ||
| 2376 | } | ||
| 2377 | |||
| 2378 | if ((ret = prof_thread_name_set(tsd, *(const char **)newp)) != | ||
| 2379 | 0) { | ||
| 2380 | goto label_return; | ||
| 2381 | } | ||
| 2382 | } else { | ||
| 2383 | const char *oldname = prof_thread_name_get(tsd); | ||
| 2384 | READ(oldname, const char *); | ||
| 2385 | } | ||
| 2386 | |||
| 2387 | ret = 0; | ||
| 2388 | label_return: | ||
| 2389 | return ret; | ||
| 2390 | } | ||
| 2391 | |||
| 2392 | static int | ||
| 2393 | thread_prof_active_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2394 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2395 | size_t newlen) { | ||
| 2396 | int ret; | ||
| 2397 | bool oldval; | ||
| 2398 | |||
| 2399 | if (!config_prof) { | ||
| 2400 | return ENOENT; | ||
| 2401 | } | ||
| 2402 | |||
| 2403 | oldval = opt_prof ? prof_thread_active_get(tsd) : false; | ||
| 2404 | if (newp != NULL) { | ||
| 2405 | if (!opt_prof) { | ||
| 2406 | ret = ENOENT; | ||
| 2407 | goto label_return; | ||
| 2408 | } | ||
| 2409 | if (newlen != sizeof(bool)) { | ||
| 2410 | ret = EINVAL; | ||
| 2411 | goto label_return; | ||
| 2412 | } | ||
| 2413 | if (prof_thread_active_set(tsd, *(bool *)newp)) { | ||
| 2414 | ret = EAGAIN; | ||
| 2415 | goto label_return; | ||
| 2416 | } | ||
| 2417 | } | ||
| 2418 | READ(oldval, bool); | ||
| 2419 | |||
| 2420 | ret = 0; | ||
| 2421 | label_return: | ||
| 2422 | return ret; | ||
| 2423 | } | ||
| 2424 | |||
| 2425 | static int | ||
| 2426 | thread_idle_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2427 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2428 | size_t newlen) { | ||
| 2429 | int ret; | ||
| 2430 | |||
| 2431 | NEITHER_READ_NOR_WRITE(); | ||
| 2432 | |||
| 2433 | if (tcache_available(tsd)) { | ||
| 2434 | tcache_flush(tsd); | ||
| 2435 | } | ||
| 2436 | /* | ||
| 2437 | * This heuristic is perhaps not the most well-considered. But it | ||
| 2438 | * matches the only idling policy we have experience with in the status | ||
| 2439 | * quo. Over time we should investigate more principled approaches. | ||
| 2440 | */ | ||
| 2441 | if (opt_narenas > ncpus * 2) { | ||
| 2442 | arena_t *arena = arena_choose(tsd, NULL); | ||
| 2443 | if (arena != NULL) { | ||
| 2444 | arena_decay(tsd_tsdn(tsd), arena, false, true); | ||
| 2445 | } | ||
| 2446 | /* | ||
| 2447 | * The missing arena case is not actually an error; a thread | ||
| 2448 | * might be idle before it associates itself to one. This is | ||
| 2449 | * unusual, but not wrong. | ||
| 2450 | */ | ||
| 2451 | } | ||
| 2452 | |||
| 2453 | ret = 0; | ||
| 2454 | label_return: | ||
| 2455 | return ret; | ||
| 2456 | } | ||
| 2457 | |||
| 2458 | /******************************************************************************/ | ||
| 2459 | |||
| 2460 | static int | ||
| 2461 | tcache_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2462 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2463 | int ret; | ||
| 2464 | unsigned tcache_ind; | ||
| 2465 | |||
| 2466 | READONLY(); | ||
| 2467 | VERIFY_READ(unsigned); | ||
| 2468 | if (tcaches_create(tsd, b0get(), &tcache_ind)) { | ||
| 2469 | ret = EFAULT; | ||
| 2470 | goto label_return; | ||
| 2471 | } | ||
| 2472 | READ(tcache_ind, unsigned); | ||
| 2473 | |||
| 2474 | ret = 0; | ||
| 2475 | label_return: | ||
| 2476 | return ret; | ||
| 2477 | } | ||
| 2478 | |||
| 2479 | static int | ||
| 2480 | tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2481 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2482 | int ret; | ||
| 2483 | unsigned tcache_ind; | ||
| 2484 | |||
| 2485 | WRITEONLY(); | ||
| 2486 | ASSURED_WRITE(tcache_ind, unsigned); | ||
| 2487 | tcaches_flush(tsd, tcache_ind); | ||
| 2488 | |||
| 2489 | ret = 0; | ||
| 2490 | label_return: | ||
| 2491 | return ret; | ||
| 2492 | } | ||
| 2493 | |||
| 2494 | static int | ||
| 2495 | tcache_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2496 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2497 | int ret; | ||
| 2498 | unsigned tcache_ind; | ||
| 2499 | |||
| 2500 | WRITEONLY(); | ||
| 2501 | ASSURED_WRITE(tcache_ind, unsigned); | ||
| 2502 | tcaches_destroy(tsd, tcache_ind); | ||
| 2503 | |||
| 2504 | ret = 0; | ||
| 2505 | label_return: | ||
| 2506 | return ret; | ||
| 2507 | } | ||
| 2508 | |||
| 2509 | /******************************************************************************/ | ||
| 2510 | |||
| 2511 | static int | ||
| 2512 | arena_i_initialized_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2513 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2514 | int ret; | ||
| 2515 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 2516 | unsigned arena_ind; | ||
| 2517 | bool initialized; | ||
| 2518 | |||
| 2519 | READONLY(); | ||
| 2520 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2521 | |||
| 2522 | malloc_mutex_lock(tsdn, &ctl_mtx); | ||
| 2523 | initialized = arenas_i(arena_ind)->initialized; | ||
| 2524 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 2525 | |||
| 2526 | READ(initialized, bool); | ||
| 2527 | |||
| 2528 | ret = 0; | ||
| 2529 | label_return: | ||
| 2530 | return ret; | ||
| 2531 | } | ||
| 2532 | |||
| 2533 | static void | ||
| 2534 | arena_i_decay(tsdn_t *tsdn, unsigned arena_ind, bool all) { | ||
| 2535 | malloc_mutex_lock(tsdn, &ctl_mtx); | ||
| 2536 | { | ||
| 2537 | unsigned narenas = ctl_arenas->narenas; | ||
| 2538 | |||
| 2539 | /* | ||
| 2540 | * Access via index narenas is deprecated, and scheduled for | ||
| 2541 | * removal in 6.0.0. | ||
| 2542 | */ | ||
| 2543 | if (arena_ind == MALLCTL_ARENAS_ALL || arena_ind == narenas) { | ||
| 2544 | unsigned i; | ||
| 2545 | VARIABLE_ARRAY(arena_t *, tarenas, narenas); | ||
| 2546 | |||
| 2547 | for (i = 0; i < narenas; i++) { | ||
| 2548 | tarenas[i] = arena_get(tsdn, i, false); | ||
| 2549 | } | ||
| 2550 | |||
| 2551 | /* | ||
| 2552 | * No further need to hold ctl_mtx, since narenas and | ||
| 2553 | * tarenas contain everything needed below. | ||
| 2554 | */ | ||
| 2555 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 2556 | |||
| 2557 | for (i = 0; i < narenas; i++) { | ||
| 2558 | if (tarenas[i] != NULL) { | ||
| 2559 | arena_decay(tsdn, tarenas[i], false, | ||
| 2560 | all); | ||
| 2561 | } | ||
| 2562 | } | ||
| 2563 | } else { | ||
| 2564 | arena_t *tarena; | ||
| 2565 | |||
| 2566 | assert(arena_ind < narenas); | ||
| 2567 | |||
| 2568 | tarena = arena_get(tsdn, arena_ind, false); | ||
| 2569 | |||
| 2570 | /* No further need to hold ctl_mtx. */ | ||
| 2571 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 2572 | |||
| 2573 | if (tarena != NULL) { | ||
| 2574 | arena_decay(tsdn, tarena, false, all); | ||
| 2575 | } | ||
| 2576 | } | ||
| 2577 | } | ||
| 2578 | } | ||
| 2579 | |||
| 2580 | static int | ||
| 2581 | arena_i_decay_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 2582 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2583 | int ret; | ||
| 2584 | unsigned arena_ind; | ||
| 2585 | |||
| 2586 | NEITHER_READ_NOR_WRITE(); | ||
| 2587 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2588 | arena_i_decay(tsd_tsdn(tsd), arena_ind, false); | ||
| 2589 | |||
| 2590 | ret = 0; | ||
| 2591 | label_return: | ||
| 2592 | return ret; | ||
| 2593 | } | ||
| 2594 | |||
| 2595 | static int | ||
| 2596 | arena_i_purge_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 2597 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2598 | int ret; | ||
| 2599 | unsigned arena_ind; | ||
| 2600 | |||
| 2601 | NEITHER_READ_NOR_WRITE(); | ||
| 2602 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2603 | arena_i_decay(tsd_tsdn(tsd), arena_ind, true); | ||
| 2604 | |||
| 2605 | ret = 0; | ||
| 2606 | label_return: | ||
| 2607 | return ret; | ||
| 2608 | } | ||
| 2609 | |||
| 2610 | static int | ||
| 2611 | arena_i_reset_destroy_helper(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2612 | void *oldp, size_t *oldlenp, void *newp, size_t newlen, unsigned *arena_ind, | ||
| 2613 | arena_t **arena) { | ||
| 2614 | int ret; | ||
| 2615 | |||
| 2616 | NEITHER_READ_NOR_WRITE(); | ||
| 2617 | MIB_UNSIGNED(*arena_ind, 1); | ||
| 2618 | |||
| 2619 | *arena = arena_get(tsd_tsdn(tsd), *arena_ind, false); | ||
| 2620 | if (*arena == NULL || arena_is_auto(*arena)) { | ||
| 2621 | ret = EFAULT; | ||
| 2622 | goto label_return; | ||
| 2623 | } | ||
| 2624 | |||
| 2625 | ret = 0; | ||
| 2626 | label_return: | ||
| 2627 | return ret; | ||
| 2628 | } | ||
| 2629 | |||
| 2630 | static void | ||
| 2631 | arena_reset_prepare_background_thread(tsd_t *tsd, unsigned arena_ind) { | ||
| 2632 | /* Temporarily disable the background thread during arena reset. */ | ||
| 2633 | if (have_background_thread) { | ||
| 2634 | malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 2635 | if (background_thread_enabled()) { | ||
| 2636 | background_thread_info_t *info = | ||
| 2637 | background_thread_info_get(arena_ind); | ||
| 2638 | assert(info->state == background_thread_started); | ||
| 2639 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 2640 | info->state = background_thread_paused; | ||
| 2641 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 2642 | } | ||
| 2643 | } | ||
| 2644 | } | ||
| 2645 | |||
| 2646 | static void | ||
| 2647 | arena_reset_finish_background_thread(tsd_t *tsd, unsigned arena_ind) { | ||
| 2648 | if (have_background_thread) { | ||
| 2649 | if (background_thread_enabled()) { | ||
| 2650 | background_thread_info_t *info = | ||
| 2651 | background_thread_info_get(arena_ind); | ||
| 2652 | assert(info->state == background_thread_paused); | ||
| 2653 | malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx); | ||
| 2654 | info->state = background_thread_started; | ||
| 2655 | malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx); | ||
| 2656 | } | ||
| 2657 | malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock); | ||
| 2658 | } | ||
| 2659 | } | ||
| 2660 | |||
| 2661 | static int | ||
| 2662 | arena_i_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 2663 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2664 | int ret; | ||
| 2665 | unsigned arena_ind; | ||
| 2666 | arena_t *arena; | ||
| 2667 | |||
| 2668 | ret = arena_i_reset_destroy_helper(tsd, mib, miblen, oldp, oldlenp, | ||
| 2669 | newp, newlen, &arena_ind, &arena); | ||
| 2670 | if (ret != 0) { | ||
| 2671 | return ret; | ||
| 2672 | } | ||
| 2673 | |||
| 2674 | arena_reset_prepare_background_thread(tsd, arena_ind); | ||
| 2675 | arena_reset(tsd, arena); | ||
| 2676 | arena_reset_finish_background_thread(tsd, arena_ind); | ||
| 2677 | |||
| 2678 | return ret; | ||
| 2679 | } | ||
| 2680 | |||
| 2681 | static int | ||
| 2682 | arena_i_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 2683 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2684 | int ret; | ||
| 2685 | unsigned arena_ind; | ||
| 2686 | arena_t *arena; | ||
| 2687 | ctl_arena_t *ctl_darena, *ctl_arena; | ||
| 2688 | |||
| 2689 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2690 | |||
| 2691 | ret = arena_i_reset_destroy_helper(tsd, mib, miblen, oldp, oldlenp, | ||
| 2692 | newp, newlen, &arena_ind, &arena); | ||
| 2693 | if (ret != 0) { | ||
| 2694 | goto label_return; | ||
| 2695 | } | ||
| 2696 | |||
| 2697 | if (arena_nthreads_get(arena, false) != 0 || arena_nthreads_get(arena, | ||
| 2698 | true) != 0) { | ||
| 2699 | ret = EFAULT; | ||
| 2700 | goto label_return; | ||
| 2701 | } | ||
| 2702 | |||
| 2703 | arena_reset_prepare_background_thread(tsd, arena_ind); | ||
| 2704 | /* Merge stats after resetting and purging arena. */ | ||
| 2705 | arena_reset(tsd, arena); | ||
| 2706 | arena_decay(tsd_tsdn(tsd), arena, false, true); | ||
| 2707 | ctl_darena = arenas_i(MALLCTL_ARENAS_DESTROYED); | ||
| 2708 | ctl_darena->initialized = true; | ||
| 2709 | ctl_arena_refresh(tsd_tsdn(tsd), arena, ctl_darena, arena_ind, true); | ||
| 2710 | /* Destroy arena. */ | ||
| 2711 | arena_destroy(tsd, arena); | ||
| 2712 | ctl_arena = arenas_i(arena_ind); | ||
| 2713 | ctl_arena->initialized = false; | ||
| 2714 | /* Record arena index for later recycling via arenas.create. */ | ||
| 2715 | ql_elm_new(ctl_arena, destroyed_link); | ||
| 2716 | ql_tail_insert(&ctl_arenas->destroyed, ctl_arena, destroyed_link); | ||
| 2717 | arena_reset_finish_background_thread(tsd, arena_ind); | ||
| 2718 | |||
| 2719 | assert(ret == 0); | ||
| 2720 | label_return: | ||
| 2721 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2722 | |||
| 2723 | return ret; | ||
| 2724 | } | ||
| 2725 | |||
| 2726 | static int | ||
| 2727 | arena_i_dss_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 2728 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2729 | int ret; | ||
| 2730 | const char *dss = NULL; | ||
| 2731 | unsigned arena_ind; | ||
| 2732 | dss_prec_t dss_prec_old = dss_prec_limit; | ||
| 2733 | dss_prec_t dss_prec = dss_prec_limit; | ||
| 2734 | |||
| 2735 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2736 | WRITE(dss, const char *); | ||
| 2737 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2738 | if (dss != NULL) { | ||
| 2739 | int i; | ||
| 2740 | bool match = false; | ||
| 2741 | |||
| 2742 | for (i = 0; i < dss_prec_limit; i++) { | ||
| 2743 | if (strcmp(dss_prec_names[i], dss) == 0) { | ||
| 2744 | dss_prec = i; | ||
| 2745 | match = true; | ||
| 2746 | break; | ||
| 2747 | } | ||
| 2748 | } | ||
| 2749 | |||
| 2750 | if (!match) { | ||
| 2751 | ret = EINVAL; | ||
| 2752 | goto label_return; | ||
| 2753 | } | ||
| 2754 | } | ||
| 2755 | |||
| 2756 | /* | ||
| 2757 | * Access via index narenas is deprecated, and scheduled for removal in | ||
| 2758 | * 6.0.0. | ||
| 2759 | */ | ||
| 2760 | if (arena_ind == MALLCTL_ARENAS_ALL || arena_ind == | ||
| 2761 | ctl_arenas->narenas) { | ||
| 2762 | if (dss_prec != dss_prec_limit && | ||
| 2763 | extent_dss_prec_set(dss_prec)) { | ||
| 2764 | ret = EFAULT; | ||
| 2765 | goto label_return; | ||
| 2766 | } | ||
| 2767 | dss_prec_old = extent_dss_prec_get(); | ||
| 2768 | } else { | ||
| 2769 | arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); | ||
| 2770 | if (arena == NULL || (dss_prec != dss_prec_limit && | ||
| 2771 | arena_dss_prec_set(arena, dss_prec))) { | ||
| 2772 | ret = EFAULT; | ||
| 2773 | goto label_return; | ||
| 2774 | } | ||
| 2775 | dss_prec_old = arena_dss_prec_get(arena); | ||
| 2776 | } | ||
| 2777 | |||
| 2778 | dss = dss_prec_names[dss_prec_old]; | ||
| 2779 | READ(dss, const char *); | ||
| 2780 | |||
| 2781 | ret = 0; | ||
| 2782 | label_return: | ||
| 2783 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2784 | return ret; | ||
| 2785 | } | ||
| 2786 | |||
| 2787 | static int | ||
| 2788 | arena_i_oversize_threshold_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2789 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2790 | int ret; | ||
| 2791 | |||
| 2792 | unsigned arena_ind; | ||
| 2793 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2794 | |||
| 2795 | arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); | ||
| 2796 | if (arena == NULL) { | ||
| 2797 | ret = EFAULT; | ||
| 2798 | goto label_return; | ||
| 2799 | } | ||
| 2800 | |||
| 2801 | if (oldp != NULL && oldlenp != NULL) { | ||
| 2802 | size_t oldval = atomic_load_zu( | ||
| 2803 | &arena->pa_shard.pac.oversize_threshold, ATOMIC_RELAXED); | ||
| 2804 | READ(oldval, size_t); | ||
| 2805 | } | ||
| 2806 | if (newp != NULL) { | ||
| 2807 | if (newlen != sizeof(size_t)) { | ||
| 2808 | ret = EINVAL; | ||
| 2809 | goto label_return; | ||
| 2810 | } | ||
| 2811 | atomic_store_zu(&arena->pa_shard.pac.oversize_threshold, | ||
| 2812 | *(size_t *)newp, ATOMIC_RELAXED); | ||
| 2813 | } | ||
| 2814 | ret = 0; | ||
| 2815 | label_return: | ||
| 2816 | return ret; | ||
| 2817 | } | ||
| 2818 | |||
| 2819 | static int | ||
| 2820 | arena_i_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2821 | void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) { | ||
| 2822 | int ret; | ||
| 2823 | unsigned arena_ind; | ||
| 2824 | arena_t *arena; | ||
| 2825 | |||
| 2826 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2827 | arena = arena_get(tsd_tsdn(tsd), arena_ind, false); | ||
| 2828 | if (arena == NULL) { | ||
| 2829 | ret = EFAULT; | ||
| 2830 | goto label_return; | ||
| 2831 | } | ||
| 2832 | extent_state_t state = dirty ? extent_state_dirty : extent_state_muzzy; | ||
| 2833 | |||
| 2834 | if (oldp != NULL && oldlenp != NULL) { | ||
| 2835 | size_t oldval = arena_decay_ms_get(arena, state); | ||
| 2836 | READ(oldval, ssize_t); | ||
| 2837 | } | ||
| 2838 | if (newp != NULL) { | ||
| 2839 | if (newlen != sizeof(ssize_t)) { | ||
| 2840 | ret = EINVAL; | ||
| 2841 | goto label_return; | ||
| 2842 | } | ||
| 2843 | if (arena_is_huge(arena_ind) && *(ssize_t *)newp > 0) { | ||
| 2844 | /* | ||
| 2845 | * By default the huge arena purges eagerly. If it is | ||
| 2846 | * set to non-zero decay time afterwards, background | ||
| 2847 | * thread might be needed. | ||
| 2848 | */ | ||
| 2849 | if (background_thread_create(tsd, arena_ind)) { | ||
| 2850 | ret = EFAULT; | ||
| 2851 | goto label_return; | ||
| 2852 | } | ||
| 2853 | } | ||
| 2854 | |||
| 2855 | if (arena_decay_ms_set(tsd_tsdn(tsd), arena, state, | ||
| 2856 | *(ssize_t *)newp)) { | ||
| 2857 | ret = EFAULT; | ||
| 2858 | goto label_return; | ||
| 2859 | } | ||
| 2860 | } | ||
| 2861 | |||
| 2862 | ret = 0; | ||
| 2863 | label_return: | ||
| 2864 | return ret; | ||
| 2865 | } | ||
| 2866 | |||
| 2867 | static int | ||
| 2868 | arena_i_dirty_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2869 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2870 | return arena_i_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, | ||
| 2871 | newlen, true); | ||
| 2872 | } | ||
| 2873 | |||
| 2874 | static int | ||
| 2875 | arena_i_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2876 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2877 | return arena_i_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, | ||
| 2878 | newlen, false); | ||
| 2879 | } | ||
| 2880 | |||
| 2881 | static int | ||
| 2882 | arena_i_extent_hooks_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 2883 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 2884 | int ret; | ||
| 2885 | unsigned arena_ind; | ||
| 2886 | arena_t *arena; | ||
| 2887 | |||
| 2888 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2889 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2890 | if (arena_ind < narenas_total_get()) { | ||
| 2891 | extent_hooks_t *old_extent_hooks; | ||
| 2892 | arena = arena_get(tsd_tsdn(tsd), arena_ind, false); | ||
| 2893 | if (arena == NULL) { | ||
| 2894 | if (arena_ind >= narenas_auto) { | ||
| 2895 | ret = EFAULT; | ||
| 2896 | goto label_return; | ||
| 2897 | } | ||
| 2898 | old_extent_hooks = | ||
| 2899 | (extent_hooks_t *)&ehooks_default_extent_hooks; | ||
| 2900 | READ(old_extent_hooks, extent_hooks_t *); | ||
| 2901 | if (newp != NULL) { | ||
| 2902 | /* Initialize a new arena as a side effect. */ | ||
| 2903 | extent_hooks_t *new_extent_hooks | ||
| 2904 | JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 2905 | WRITE(new_extent_hooks, extent_hooks_t *); | ||
| 2906 | arena_config_t config = arena_config_default; | ||
| 2907 | config.extent_hooks = new_extent_hooks; | ||
| 2908 | |||
| 2909 | arena = arena_init(tsd_tsdn(tsd), arena_ind, | ||
| 2910 | &config); | ||
| 2911 | if (arena == NULL) { | ||
| 2912 | ret = EFAULT; | ||
| 2913 | goto label_return; | ||
| 2914 | } | ||
| 2915 | } | ||
| 2916 | } else { | ||
| 2917 | if (newp != NULL) { | ||
| 2918 | extent_hooks_t *new_extent_hooks | ||
| 2919 | JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 2920 | WRITE(new_extent_hooks, extent_hooks_t *); | ||
| 2921 | old_extent_hooks = arena_set_extent_hooks(tsd, | ||
| 2922 | arena, new_extent_hooks); | ||
| 2923 | READ(old_extent_hooks, extent_hooks_t *); | ||
| 2924 | } else { | ||
| 2925 | old_extent_hooks = | ||
| 2926 | ehooks_get_extent_hooks_ptr( | ||
| 2927 | arena_get_ehooks(arena)); | ||
| 2928 | READ(old_extent_hooks, extent_hooks_t *); | ||
| 2929 | } | ||
| 2930 | } | ||
| 2931 | } else { | ||
| 2932 | ret = EFAULT; | ||
| 2933 | goto label_return; | ||
| 2934 | } | ||
| 2935 | ret = 0; | ||
| 2936 | label_return: | ||
| 2937 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2938 | return ret; | ||
| 2939 | } | ||
| 2940 | |||
| 2941 | static int | ||
| 2942 | arena_i_retain_grow_limit_ctl(tsd_t *tsd, const size_t *mib, | ||
| 2943 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 2944 | size_t newlen) { | ||
| 2945 | int ret; | ||
| 2946 | unsigned arena_ind; | ||
| 2947 | arena_t *arena; | ||
| 2948 | |||
| 2949 | if (!opt_retain) { | ||
| 2950 | /* Only relevant when retain is enabled. */ | ||
| 2951 | return ENOENT; | ||
| 2952 | } | ||
| 2953 | |||
| 2954 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2955 | MIB_UNSIGNED(arena_ind, 1); | ||
| 2956 | if (arena_ind < narenas_total_get() && (arena = | ||
| 2957 | arena_get(tsd_tsdn(tsd), arena_ind, false)) != NULL) { | ||
| 2958 | size_t old_limit, new_limit; | ||
| 2959 | if (newp != NULL) { | ||
| 2960 | WRITE(new_limit, size_t); | ||
| 2961 | } | ||
| 2962 | bool err = arena_retain_grow_limit_get_set(tsd, arena, | ||
| 2963 | &old_limit, newp != NULL ? &new_limit : NULL); | ||
| 2964 | if (!err) { | ||
| 2965 | READ(old_limit, size_t); | ||
| 2966 | ret = 0; | ||
| 2967 | } else { | ||
| 2968 | ret = EFAULT; | ||
| 2969 | } | ||
| 2970 | } else { | ||
| 2971 | ret = EFAULT; | ||
| 2972 | } | ||
| 2973 | label_return: | ||
| 2974 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 2975 | return ret; | ||
| 2976 | } | ||
| 2977 | |||
| 2978 | static const ctl_named_node_t * | ||
| 2979 | arena_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, | ||
| 2980 | size_t i) { | ||
| 2981 | const ctl_named_node_t *ret; | ||
| 2982 | |||
| 2983 | malloc_mutex_lock(tsdn, &ctl_mtx); | ||
| 2984 | switch (i) { | ||
| 2985 | case MALLCTL_ARENAS_ALL: | ||
| 2986 | case MALLCTL_ARENAS_DESTROYED: | ||
| 2987 | break; | ||
| 2988 | default: | ||
| 2989 | if (i > ctl_arenas->narenas) { | ||
| 2990 | ret = NULL; | ||
| 2991 | goto label_return; | ||
| 2992 | } | ||
| 2993 | break; | ||
| 2994 | } | ||
| 2995 | |||
| 2996 | ret = super_arena_i_node; | ||
| 2997 | label_return: | ||
| 2998 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 2999 | return ret; | ||
| 3000 | } | ||
| 3001 | |||
| 3002 | /******************************************************************************/ | ||
| 3003 | |||
| 3004 | static int | ||
| 3005 | arenas_narenas_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3006 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3007 | int ret; | ||
| 3008 | unsigned narenas; | ||
| 3009 | |||
| 3010 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3011 | READONLY(); | ||
| 3012 | narenas = ctl_arenas->narenas; | ||
| 3013 | READ(narenas, unsigned); | ||
| 3014 | |||
| 3015 | ret = 0; | ||
| 3016 | label_return: | ||
| 3017 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3018 | return ret; | ||
| 3019 | } | ||
| 3020 | |||
| 3021 | static int | ||
| 3022 | arenas_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, | ||
| 3023 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 3024 | size_t newlen, bool dirty) { | ||
| 3025 | int ret; | ||
| 3026 | |||
| 3027 | if (oldp != NULL && oldlenp != NULL) { | ||
| 3028 | size_t oldval = (dirty ? arena_dirty_decay_ms_default_get() : | ||
| 3029 | arena_muzzy_decay_ms_default_get()); | ||
| 3030 | READ(oldval, ssize_t); | ||
| 3031 | } | ||
| 3032 | if (newp != NULL) { | ||
| 3033 | if (newlen != sizeof(ssize_t)) { | ||
| 3034 | ret = EINVAL; | ||
| 3035 | goto label_return; | ||
| 3036 | } | ||
| 3037 | if (dirty ? arena_dirty_decay_ms_default_set(*(ssize_t *)newp) | ||
| 3038 | : arena_muzzy_decay_ms_default_set(*(ssize_t *)newp)) { | ||
| 3039 | ret = EFAULT; | ||
| 3040 | goto label_return; | ||
| 3041 | } | ||
| 3042 | } | ||
| 3043 | |||
| 3044 | ret = 0; | ||
| 3045 | label_return: | ||
| 3046 | return ret; | ||
| 3047 | } | ||
| 3048 | |||
| 3049 | static int | ||
| 3050 | arenas_dirty_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3051 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3052 | return arenas_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, | ||
| 3053 | newlen, true); | ||
| 3054 | } | ||
| 3055 | |||
| 3056 | static int | ||
| 3057 | arenas_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3058 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3059 | return arenas_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp, | ||
| 3060 | newlen, false); | ||
| 3061 | } | ||
| 3062 | |||
| 3063 | CTL_RO_NL_GEN(arenas_quantum, QUANTUM, size_t) | ||
| 3064 | CTL_RO_NL_GEN(arenas_page, PAGE, size_t) | ||
| 3065 | CTL_RO_NL_GEN(arenas_tcache_max, tcache_maxclass, size_t) | ||
| 3066 | CTL_RO_NL_GEN(arenas_nbins, SC_NBINS, unsigned) | ||
| 3067 | CTL_RO_NL_GEN(arenas_nhbins, nhbins, unsigned) | ||
| 3068 | CTL_RO_NL_GEN(arenas_bin_i_size, bin_infos[mib[2]].reg_size, size_t) | ||
| 3069 | CTL_RO_NL_GEN(arenas_bin_i_nregs, bin_infos[mib[2]].nregs, uint32_t) | ||
| 3070 | CTL_RO_NL_GEN(arenas_bin_i_slab_size, bin_infos[mib[2]].slab_size, size_t) | ||
| 3071 | CTL_RO_NL_GEN(arenas_bin_i_nshards, bin_infos[mib[2]].n_shards, uint32_t) | ||
| 3072 | static const ctl_named_node_t * | ||
| 3073 | arenas_bin_i_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3074 | size_t miblen, size_t i) { | ||
| 3075 | if (i > SC_NBINS) { | ||
| 3076 | return NULL; | ||
| 3077 | } | ||
| 3078 | return super_arenas_bin_i_node; | ||
| 3079 | } | ||
| 3080 | |||
| 3081 | CTL_RO_NL_GEN(arenas_nlextents, SC_NSIZES - SC_NBINS, unsigned) | ||
| 3082 | CTL_RO_NL_GEN(arenas_lextent_i_size, sz_index2size(SC_NBINS+(szind_t)mib[2]), | ||
| 3083 | size_t) | ||
| 3084 | static const ctl_named_node_t * | ||
| 3085 | arenas_lextent_i_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3086 | size_t miblen, size_t i) { | ||
| 3087 | if (i > SC_NSIZES - SC_NBINS) { | ||
| 3088 | return NULL; | ||
| 3089 | } | ||
| 3090 | return super_arenas_lextent_i_node; | ||
| 3091 | } | ||
| 3092 | |||
| 3093 | static int | ||
| 3094 | arenas_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3095 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3096 | int ret; | ||
| 3097 | unsigned arena_ind; | ||
| 3098 | |||
| 3099 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3100 | |||
| 3101 | VERIFY_READ(unsigned); | ||
| 3102 | arena_config_t config = arena_config_default; | ||
| 3103 | WRITE(config.extent_hooks, extent_hooks_t *); | ||
| 3104 | if ((arena_ind = ctl_arena_init(tsd, &config)) == UINT_MAX) { | ||
| 3105 | ret = EAGAIN; | ||
| 3106 | goto label_return; | ||
| 3107 | } | ||
| 3108 | READ(arena_ind, unsigned); | ||
| 3109 | |||
| 3110 | ret = 0; | ||
| 3111 | label_return: | ||
| 3112 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3113 | return ret; | ||
| 3114 | } | ||
| 3115 | |||
| 3116 | static int | ||
| 3117 | experimental_arenas_create_ext_ctl(tsd_t *tsd, | ||
| 3118 | const size_t *mib, size_t miblen, | ||
| 3119 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3120 | int ret; | ||
| 3121 | unsigned arena_ind; | ||
| 3122 | |||
| 3123 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3124 | |||
| 3125 | arena_config_t config = arena_config_default; | ||
| 3126 | VERIFY_READ(unsigned); | ||
| 3127 | WRITE(config, arena_config_t); | ||
| 3128 | |||
| 3129 | if ((arena_ind = ctl_arena_init(tsd, &config)) == UINT_MAX) { | ||
| 3130 | ret = EAGAIN; | ||
| 3131 | goto label_return; | ||
| 3132 | } | ||
| 3133 | READ(arena_ind, unsigned); | ||
| 3134 | ret = 0; | ||
| 3135 | label_return: | ||
| 3136 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3137 | return ret; | ||
| 3138 | } | ||
| 3139 | |||
| 3140 | static int | ||
| 3141 | arenas_lookup_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3142 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 3143 | size_t newlen) { | ||
| 3144 | int ret; | ||
| 3145 | unsigned arena_ind; | ||
| 3146 | void *ptr; | ||
| 3147 | edata_t *edata; | ||
| 3148 | arena_t *arena; | ||
| 3149 | |||
| 3150 | ptr = NULL; | ||
| 3151 | ret = EINVAL; | ||
| 3152 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3153 | WRITE(ptr, void *); | ||
| 3154 | edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr); | ||
| 3155 | if (edata == NULL) { | ||
| 3156 | goto label_return; | ||
| 3157 | } | ||
| 3158 | |||
| 3159 | arena = arena_get_from_edata(edata); | ||
| 3160 | if (arena == NULL) { | ||
| 3161 | goto label_return; | ||
| 3162 | } | ||
| 3163 | |||
| 3164 | arena_ind = arena_ind_get(arena); | ||
| 3165 | READ(arena_ind, unsigned); | ||
| 3166 | |||
| 3167 | ret = 0; | ||
| 3168 | label_return: | ||
| 3169 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3170 | return ret; | ||
| 3171 | } | ||
| 3172 | |||
| 3173 | /******************************************************************************/ | ||
| 3174 | |||
| 3175 | static int | ||
| 3176 | prof_thread_active_init_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3177 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, | ||
| 3178 | size_t newlen) { | ||
| 3179 | int ret; | ||
| 3180 | bool oldval; | ||
| 3181 | |||
| 3182 | if (!config_prof) { | ||
| 3183 | return ENOENT; | ||
| 3184 | } | ||
| 3185 | |||
| 3186 | if (newp != NULL) { | ||
| 3187 | if (!opt_prof) { | ||
| 3188 | ret = ENOENT; | ||
| 3189 | goto label_return; | ||
| 3190 | } | ||
| 3191 | if (newlen != sizeof(bool)) { | ||
| 3192 | ret = EINVAL; | ||
| 3193 | goto label_return; | ||
| 3194 | } | ||
| 3195 | oldval = prof_thread_active_init_set(tsd_tsdn(tsd), | ||
| 3196 | *(bool *)newp); | ||
| 3197 | } else { | ||
| 3198 | oldval = opt_prof ? prof_thread_active_init_get(tsd_tsdn(tsd)) : | ||
| 3199 | false; | ||
| 3200 | } | ||
| 3201 | READ(oldval, bool); | ||
| 3202 | |||
| 3203 | ret = 0; | ||
| 3204 | label_return: | ||
| 3205 | return ret; | ||
| 3206 | } | ||
| 3207 | |||
| 3208 | static int | ||
| 3209 | prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3210 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3211 | int ret; | ||
| 3212 | bool oldval; | ||
| 3213 | |||
| 3214 | if (!config_prof) { | ||
| 3215 | ret = ENOENT; | ||
| 3216 | goto label_return; | ||
| 3217 | } | ||
| 3218 | |||
| 3219 | if (newp != NULL) { | ||
| 3220 | if (newlen != sizeof(bool)) { | ||
| 3221 | ret = EINVAL; | ||
| 3222 | goto label_return; | ||
| 3223 | } | ||
| 3224 | bool val = *(bool *)newp; | ||
| 3225 | if (!opt_prof) { | ||
| 3226 | if (val) { | ||
| 3227 | ret = ENOENT; | ||
| 3228 | goto label_return; | ||
| 3229 | } else { | ||
| 3230 | /* No change needed (already off). */ | ||
| 3231 | oldval = false; | ||
| 3232 | } | ||
| 3233 | } else { | ||
| 3234 | oldval = prof_active_set(tsd_tsdn(tsd), val); | ||
| 3235 | } | ||
| 3236 | } else { | ||
| 3237 | oldval = opt_prof ? prof_active_get(tsd_tsdn(tsd)) : false; | ||
| 3238 | } | ||
| 3239 | READ(oldval, bool); | ||
| 3240 | |||
| 3241 | ret = 0; | ||
| 3242 | label_return: | ||
| 3243 | return ret; | ||
| 3244 | } | ||
| 3245 | |||
| 3246 | static int | ||
| 3247 | prof_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3248 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3249 | int ret; | ||
| 3250 | const char *filename = NULL; | ||
| 3251 | |||
| 3252 | if (!config_prof || !opt_prof) { | ||
| 3253 | return ENOENT; | ||
| 3254 | } | ||
| 3255 | |||
| 3256 | WRITEONLY(); | ||
| 3257 | WRITE(filename, const char *); | ||
| 3258 | |||
| 3259 | if (prof_mdump(tsd, filename)) { | ||
| 3260 | ret = EFAULT; | ||
| 3261 | goto label_return; | ||
| 3262 | } | ||
| 3263 | |||
| 3264 | ret = 0; | ||
| 3265 | label_return: | ||
| 3266 | return ret; | ||
| 3267 | } | ||
| 3268 | |||
| 3269 | static int | ||
| 3270 | prof_gdump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3271 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3272 | int ret; | ||
| 3273 | bool oldval; | ||
| 3274 | |||
| 3275 | if (!config_prof) { | ||
| 3276 | return ENOENT; | ||
| 3277 | } | ||
| 3278 | |||
| 3279 | if (newp != NULL) { | ||
| 3280 | if (!opt_prof) { | ||
| 3281 | ret = ENOENT; | ||
| 3282 | goto label_return; | ||
| 3283 | } | ||
| 3284 | if (newlen != sizeof(bool)) { | ||
| 3285 | ret = EINVAL; | ||
| 3286 | goto label_return; | ||
| 3287 | } | ||
| 3288 | oldval = prof_gdump_set(tsd_tsdn(tsd), *(bool *)newp); | ||
| 3289 | } else { | ||
| 3290 | oldval = opt_prof ? prof_gdump_get(tsd_tsdn(tsd)) : false; | ||
| 3291 | } | ||
| 3292 | READ(oldval, bool); | ||
| 3293 | |||
| 3294 | ret = 0; | ||
| 3295 | label_return: | ||
| 3296 | return ret; | ||
| 3297 | } | ||
| 3298 | |||
| 3299 | static int | ||
| 3300 | prof_prefix_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3301 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3302 | int ret; | ||
| 3303 | const char *prefix = NULL; | ||
| 3304 | |||
| 3305 | if (!config_prof || !opt_prof) { | ||
| 3306 | return ENOENT; | ||
| 3307 | } | ||
| 3308 | |||
| 3309 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3310 | WRITEONLY(); | ||
| 3311 | WRITE(prefix, const char *); | ||
| 3312 | |||
| 3313 | ret = prof_prefix_set(tsd_tsdn(tsd), prefix) ? EFAULT : 0; | ||
| 3314 | label_return: | ||
| 3315 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 3316 | return ret; | ||
| 3317 | } | ||
| 3318 | |||
| 3319 | static int | ||
| 3320 | prof_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3321 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3322 | int ret; | ||
| 3323 | size_t lg_sample = lg_prof_sample; | ||
| 3324 | |||
| 3325 | if (!config_prof || !opt_prof) { | ||
| 3326 | return ENOENT; | ||
| 3327 | } | ||
| 3328 | |||
| 3329 | WRITEONLY(); | ||
| 3330 | WRITE(lg_sample, size_t); | ||
| 3331 | if (lg_sample >= (sizeof(uint64_t) << 3)) { | ||
| 3332 | lg_sample = (sizeof(uint64_t) << 3) - 1; | ||
| 3333 | } | ||
| 3334 | |||
| 3335 | prof_reset(tsd, lg_sample); | ||
| 3336 | |||
| 3337 | ret = 0; | ||
| 3338 | label_return: | ||
| 3339 | return ret; | ||
| 3340 | } | ||
| 3341 | |||
| 3342 | CTL_RO_NL_CGEN(config_prof, prof_interval, prof_interval, uint64_t) | ||
| 3343 | CTL_RO_NL_CGEN(config_prof, lg_prof_sample, lg_prof_sample, size_t) | ||
| 3344 | |||
| 3345 | static int | ||
| 3346 | prof_log_start_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 3347 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3348 | int ret; | ||
| 3349 | |||
| 3350 | const char *filename = NULL; | ||
| 3351 | |||
| 3352 | if (!config_prof || !opt_prof) { | ||
| 3353 | return ENOENT; | ||
| 3354 | } | ||
| 3355 | |||
| 3356 | WRITEONLY(); | ||
| 3357 | WRITE(filename, const char *); | ||
| 3358 | |||
| 3359 | if (prof_log_start(tsd_tsdn(tsd), filename)) { | ||
| 3360 | ret = EFAULT; | ||
| 3361 | goto label_return; | ||
| 3362 | } | ||
| 3363 | |||
| 3364 | ret = 0; | ||
| 3365 | label_return: | ||
| 3366 | return ret; | ||
| 3367 | } | ||
| 3368 | |||
| 3369 | static int | ||
| 3370 | prof_log_stop_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, | ||
| 3371 | size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3372 | if (!config_prof || !opt_prof) { | ||
| 3373 | return ENOENT; | ||
| 3374 | } | ||
| 3375 | |||
| 3376 | if (prof_log_stop(tsd_tsdn(tsd))) { | ||
| 3377 | return EFAULT; | ||
| 3378 | } | ||
| 3379 | |||
| 3380 | return 0; | ||
| 3381 | } | ||
| 3382 | |||
| 3383 | static int | ||
| 3384 | experimental_hooks_prof_backtrace_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3385 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3386 | int ret; | ||
| 3387 | |||
| 3388 | if (oldp == NULL && newp == NULL) { | ||
| 3389 | ret = EINVAL; | ||
| 3390 | goto label_return; | ||
| 3391 | } | ||
| 3392 | if (oldp != NULL) { | ||
| 3393 | prof_backtrace_hook_t old_hook = | ||
| 3394 | prof_backtrace_hook_get(); | ||
| 3395 | READ(old_hook, prof_backtrace_hook_t); | ||
| 3396 | } | ||
| 3397 | if (newp != NULL) { | ||
| 3398 | if (!opt_prof) { | ||
| 3399 | ret = ENOENT; | ||
| 3400 | goto label_return; | ||
| 3401 | } | ||
| 3402 | prof_backtrace_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 3403 | WRITE(new_hook, prof_backtrace_hook_t); | ||
| 3404 | if (new_hook == NULL) { | ||
| 3405 | ret = EINVAL; | ||
| 3406 | goto label_return; | ||
| 3407 | } | ||
| 3408 | prof_backtrace_hook_set(new_hook); | ||
| 3409 | } | ||
| 3410 | ret = 0; | ||
| 3411 | label_return: | ||
| 3412 | return ret; | ||
| 3413 | } | ||
| 3414 | |||
| 3415 | static int | ||
| 3416 | experimental_hooks_prof_dump_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3417 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3418 | int ret; | ||
| 3419 | |||
| 3420 | if (oldp == NULL && newp == NULL) { | ||
| 3421 | ret = EINVAL; | ||
| 3422 | goto label_return; | ||
| 3423 | } | ||
| 3424 | if (oldp != NULL) { | ||
| 3425 | prof_dump_hook_t old_hook = | ||
| 3426 | prof_dump_hook_get(); | ||
| 3427 | READ(old_hook, prof_dump_hook_t); | ||
| 3428 | } | ||
| 3429 | if (newp != NULL) { | ||
| 3430 | if (!opt_prof) { | ||
| 3431 | ret = ENOENT; | ||
| 3432 | goto label_return; | ||
| 3433 | } | ||
| 3434 | prof_dump_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 3435 | WRITE(new_hook, prof_dump_hook_t); | ||
| 3436 | prof_dump_hook_set(new_hook); | ||
| 3437 | } | ||
| 3438 | ret = 0; | ||
| 3439 | label_return: | ||
| 3440 | return ret; | ||
| 3441 | } | ||
| 3442 | |||
| 3443 | /* For integration test purpose only. No plan to move out of experimental. */ | ||
| 3444 | static int | ||
| 3445 | experimental_hooks_safety_check_abort_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3446 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3447 | int ret; | ||
| 3448 | |||
| 3449 | WRITEONLY(); | ||
| 3450 | if (newp != NULL) { | ||
| 3451 | if (newlen != sizeof(safety_check_abort_hook_t)) { | ||
| 3452 | ret = EINVAL; | ||
| 3453 | goto label_return; | ||
| 3454 | } | ||
| 3455 | safety_check_abort_hook_t hook JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 3456 | WRITE(hook, safety_check_abort_hook_t); | ||
| 3457 | safety_check_set_abort(hook); | ||
| 3458 | } | ||
| 3459 | ret = 0; | ||
| 3460 | label_return: | ||
| 3461 | return ret; | ||
| 3462 | } | ||
| 3463 | |||
| 3464 | /******************************************************************************/ | ||
| 3465 | |||
| 3466 | CTL_RO_CGEN(config_stats, stats_allocated, ctl_stats->allocated, size_t) | ||
| 3467 | CTL_RO_CGEN(config_stats, stats_active, ctl_stats->active, size_t) | ||
| 3468 | CTL_RO_CGEN(config_stats, stats_metadata, ctl_stats->metadata, size_t) | ||
| 3469 | CTL_RO_CGEN(config_stats, stats_metadata_thp, ctl_stats->metadata_thp, size_t) | ||
| 3470 | CTL_RO_CGEN(config_stats, stats_resident, ctl_stats->resident, size_t) | ||
| 3471 | CTL_RO_CGEN(config_stats, stats_mapped, ctl_stats->mapped, size_t) | ||
| 3472 | CTL_RO_CGEN(config_stats, stats_retained, ctl_stats->retained, size_t) | ||
| 3473 | |||
| 3474 | CTL_RO_CGEN(config_stats, stats_background_thread_num_threads, | ||
| 3475 | ctl_stats->background_thread.num_threads, size_t) | ||
| 3476 | CTL_RO_CGEN(config_stats, stats_background_thread_num_runs, | ||
| 3477 | ctl_stats->background_thread.num_runs, uint64_t) | ||
| 3478 | CTL_RO_CGEN(config_stats, stats_background_thread_run_interval, | ||
| 3479 | nstime_ns(&ctl_stats->background_thread.run_interval), uint64_t) | ||
| 3480 | |||
| 3481 | CTL_RO_CGEN(config_stats, stats_zero_reallocs, | ||
| 3482 | atomic_load_zu(&zero_realloc_count, ATOMIC_RELAXED), size_t) | ||
| 3483 | |||
| 3484 | CTL_RO_GEN(stats_arenas_i_dss, arenas_i(mib[2])->dss, const char *) | ||
| 3485 | CTL_RO_GEN(stats_arenas_i_dirty_decay_ms, arenas_i(mib[2])->dirty_decay_ms, | ||
| 3486 | ssize_t) | ||
| 3487 | CTL_RO_GEN(stats_arenas_i_muzzy_decay_ms, arenas_i(mib[2])->muzzy_decay_ms, | ||
| 3488 | ssize_t) | ||
| 3489 | CTL_RO_GEN(stats_arenas_i_nthreads, arenas_i(mib[2])->nthreads, unsigned) | ||
| 3490 | CTL_RO_GEN(stats_arenas_i_uptime, | ||
| 3491 | nstime_ns(&arenas_i(mib[2])->astats->astats.uptime), uint64_t) | ||
| 3492 | CTL_RO_GEN(stats_arenas_i_pactive, arenas_i(mib[2])->pactive, size_t) | ||
| 3493 | CTL_RO_GEN(stats_arenas_i_pdirty, arenas_i(mib[2])->pdirty, size_t) | ||
| 3494 | CTL_RO_GEN(stats_arenas_i_pmuzzy, arenas_i(mib[2])->pmuzzy, size_t) | ||
| 3495 | CTL_RO_CGEN(config_stats, stats_arenas_i_mapped, | ||
| 3496 | arenas_i(mib[2])->astats->astats.mapped, size_t) | ||
| 3497 | CTL_RO_CGEN(config_stats, stats_arenas_i_retained, | ||
| 3498 | arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.retained, size_t) | ||
| 3499 | CTL_RO_CGEN(config_stats, stats_arenas_i_extent_avail, | ||
| 3500 | arenas_i(mib[2])->astats->astats.pa_shard_stats.edata_avail, size_t) | ||
| 3501 | |||
| 3502 | CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_npurge, | ||
| 3503 | locked_read_u64_unsynchronized( | ||
| 3504 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_dirty.npurge), | ||
| 3505 | uint64_t) | ||
| 3506 | CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_nmadvise, | ||
| 3507 | locked_read_u64_unsynchronized( | ||
| 3508 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_dirty.nmadvise), | ||
| 3509 | uint64_t) | ||
| 3510 | CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_purged, | ||
| 3511 | locked_read_u64_unsynchronized( | ||
| 3512 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_dirty.purged), | ||
| 3513 | uint64_t) | ||
| 3514 | |||
| 3515 | CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_npurge, | ||
| 3516 | locked_read_u64_unsynchronized( | ||
| 3517 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_muzzy.npurge), | ||
| 3518 | uint64_t) | ||
| 3519 | CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_nmadvise, | ||
| 3520 | locked_read_u64_unsynchronized( | ||
| 3521 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_muzzy.nmadvise), | ||
| 3522 | uint64_t) | ||
| 3523 | CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_purged, | ||
| 3524 | locked_read_u64_unsynchronized( | ||
| 3525 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.decay_muzzy.purged), | ||
| 3526 | uint64_t) | ||
| 3527 | |||
| 3528 | CTL_RO_CGEN(config_stats, stats_arenas_i_base, | ||
| 3529 | arenas_i(mib[2])->astats->astats.base, | ||
| 3530 | size_t) | ||
| 3531 | CTL_RO_CGEN(config_stats, stats_arenas_i_internal, | ||
| 3532 | atomic_load_zu(&arenas_i(mib[2])->astats->astats.internal, ATOMIC_RELAXED), | ||
| 3533 | size_t) | ||
| 3534 | CTL_RO_CGEN(config_stats, stats_arenas_i_metadata_thp, | ||
| 3535 | arenas_i(mib[2])->astats->astats.metadata_thp, size_t) | ||
| 3536 | CTL_RO_CGEN(config_stats, stats_arenas_i_tcache_bytes, | ||
| 3537 | arenas_i(mib[2])->astats->astats.tcache_bytes, size_t) | ||
| 3538 | CTL_RO_CGEN(config_stats, stats_arenas_i_tcache_stashed_bytes, | ||
| 3539 | arenas_i(mib[2])->astats->astats.tcache_stashed_bytes, size_t) | ||
| 3540 | CTL_RO_CGEN(config_stats, stats_arenas_i_resident, | ||
| 3541 | arenas_i(mib[2])->astats->astats.resident, | ||
| 3542 | size_t) | ||
| 3543 | CTL_RO_CGEN(config_stats, stats_arenas_i_abandoned_vm, | ||
| 3544 | atomic_load_zu( | ||
| 3545 | &arenas_i(mib[2])->astats->astats.pa_shard_stats.pac_stats.abandoned_vm, | ||
| 3546 | ATOMIC_RELAXED), size_t) | ||
| 3547 | |||
| 3548 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_sec_bytes, | ||
| 3549 | arenas_i(mib[2])->astats->secstats.bytes, size_t) | ||
| 3550 | |||
| 3551 | CTL_RO_CGEN(config_stats, stats_arenas_i_small_allocated, | ||
| 3552 | arenas_i(mib[2])->astats->allocated_small, size_t) | ||
| 3553 | CTL_RO_CGEN(config_stats, stats_arenas_i_small_nmalloc, | ||
| 3554 | arenas_i(mib[2])->astats->nmalloc_small, uint64_t) | ||
| 3555 | CTL_RO_CGEN(config_stats, stats_arenas_i_small_ndalloc, | ||
| 3556 | arenas_i(mib[2])->astats->ndalloc_small, uint64_t) | ||
| 3557 | CTL_RO_CGEN(config_stats, stats_arenas_i_small_nrequests, | ||
| 3558 | arenas_i(mib[2])->astats->nrequests_small, uint64_t) | ||
| 3559 | CTL_RO_CGEN(config_stats, stats_arenas_i_small_nfills, | ||
| 3560 | arenas_i(mib[2])->astats->nfills_small, uint64_t) | ||
| 3561 | CTL_RO_CGEN(config_stats, stats_arenas_i_small_nflushes, | ||
| 3562 | arenas_i(mib[2])->astats->nflushes_small, uint64_t) | ||
| 3563 | CTL_RO_CGEN(config_stats, stats_arenas_i_large_allocated, | ||
| 3564 | arenas_i(mib[2])->astats->astats.allocated_large, size_t) | ||
| 3565 | CTL_RO_CGEN(config_stats, stats_arenas_i_large_nmalloc, | ||
| 3566 | arenas_i(mib[2])->astats->astats.nmalloc_large, uint64_t) | ||
| 3567 | CTL_RO_CGEN(config_stats, stats_arenas_i_large_ndalloc, | ||
| 3568 | arenas_i(mib[2])->astats->astats.ndalloc_large, uint64_t) | ||
| 3569 | CTL_RO_CGEN(config_stats, stats_arenas_i_large_nrequests, | ||
| 3570 | arenas_i(mib[2])->astats->astats.nrequests_large, uint64_t) | ||
| 3571 | /* | ||
| 3572 | * Note: "nmalloc_large" here instead of "nfills" in the read. This is | ||
| 3573 | * intentional (large has no batch fill). | ||
| 3574 | */ | ||
| 3575 | CTL_RO_CGEN(config_stats, stats_arenas_i_large_nfills, | ||
| 3576 | arenas_i(mib[2])->astats->astats.nmalloc_large, uint64_t) | ||
| 3577 | CTL_RO_CGEN(config_stats, stats_arenas_i_large_nflushes, | ||
| 3578 | arenas_i(mib[2])->astats->astats.nflushes_large, uint64_t) | ||
| 3579 | |||
| 3580 | /* Lock profiling related APIs below. */ | ||
| 3581 | #define RO_MUTEX_CTL_GEN(n, l) \ | ||
| 3582 | CTL_RO_CGEN(config_stats, stats_##n##_num_ops, \ | ||
| 3583 | l.n_lock_ops, uint64_t) \ | ||
| 3584 | CTL_RO_CGEN(config_stats, stats_##n##_num_wait, \ | ||
| 3585 | l.n_wait_times, uint64_t) \ | ||
| 3586 | CTL_RO_CGEN(config_stats, stats_##n##_num_spin_acq, \ | ||
| 3587 | l.n_spin_acquired, uint64_t) \ | ||
| 3588 | CTL_RO_CGEN(config_stats, stats_##n##_num_owner_switch, \ | ||
| 3589 | l.n_owner_switches, uint64_t) \ | ||
| 3590 | CTL_RO_CGEN(config_stats, stats_##n##_total_wait_time, \ | ||
| 3591 | nstime_ns(&l.tot_wait_time), uint64_t) \ | ||
| 3592 | CTL_RO_CGEN(config_stats, stats_##n##_max_wait_time, \ | ||
| 3593 | nstime_ns(&l.max_wait_time), uint64_t) \ | ||
| 3594 | CTL_RO_CGEN(config_stats, stats_##n##_max_num_thds, \ | ||
| 3595 | l.max_n_thds, uint32_t) | ||
| 3596 | |||
| 3597 | /* Global mutexes. */ | ||
| 3598 | #define OP(mtx) \ | ||
| 3599 | RO_MUTEX_CTL_GEN(mutexes_##mtx, \ | ||
| 3600 | ctl_stats->mutex_prof_data[global_prof_mutex_##mtx]) | ||
| 3601 | MUTEX_PROF_GLOBAL_MUTEXES | ||
| 3602 | #undef OP | ||
| 3603 | |||
| 3604 | /* Per arena mutexes */ | ||
| 3605 | #define OP(mtx) RO_MUTEX_CTL_GEN(arenas_i_mutexes_##mtx, \ | ||
| 3606 | arenas_i(mib[2])->astats->astats.mutex_prof_data[arena_prof_mutex_##mtx]) | ||
| 3607 | MUTEX_PROF_ARENA_MUTEXES | ||
| 3608 | #undef OP | ||
| 3609 | |||
| 3610 | /* tcache bin mutex */ | ||
| 3611 | RO_MUTEX_CTL_GEN(arenas_i_bins_j_mutex, | ||
| 3612 | arenas_i(mib[2])->astats->bstats[mib[4]].mutex_data) | ||
| 3613 | #undef RO_MUTEX_CTL_GEN | ||
| 3614 | |||
| 3615 | /* Resets all mutex stats, including global, arena and bin mutexes. */ | ||
| 3616 | static int | ||
| 3617 | stats_mutexes_reset_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3618 | size_t miblen, void *oldp, size_t *oldlenp, | ||
| 3619 | void *newp, size_t newlen) { | ||
| 3620 | if (!config_stats) { | ||
| 3621 | return ENOENT; | ||
| 3622 | } | ||
| 3623 | |||
| 3624 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 3625 | |||
| 3626 | #define MUTEX_PROF_RESET(mtx) \ | ||
| 3627 | malloc_mutex_lock(tsdn, &mtx); \ | ||
| 3628 | malloc_mutex_prof_data_reset(tsdn, &mtx); \ | ||
| 3629 | malloc_mutex_unlock(tsdn, &mtx); | ||
| 3630 | |||
| 3631 | /* Global mutexes: ctl and prof. */ | ||
| 3632 | MUTEX_PROF_RESET(ctl_mtx); | ||
| 3633 | if (have_background_thread) { | ||
| 3634 | MUTEX_PROF_RESET(background_thread_lock); | ||
| 3635 | } | ||
| 3636 | if (config_prof && opt_prof) { | ||
| 3637 | MUTEX_PROF_RESET(bt2gctx_mtx); | ||
| 3638 | MUTEX_PROF_RESET(tdatas_mtx); | ||
| 3639 | MUTEX_PROF_RESET(prof_dump_mtx); | ||
| 3640 | MUTEX_PROF_RESET(prof_recent_alloc_mtx); | ||
| 3641 | MUTEX_PROF_RESET(prof_recent_dump_mtx); | ||
| 3642 | MUTEX_PROF_RESET(prof_stats_mtx); | ||
| 3643 | } | ||
| 3644 | |||
| 3645 | /* Per arena mutexes. */ | ||
| 3646 | unsigned n = narenas_total_get(); | ||
| 3647 | |||
| 3648 | for (unsigned i = 0; i < n; i++) { | ||
| 3649 | arena_t *arena = arena_get(tsdn, i, false); | ||
| 3650 | if (!arena) { | ||
| 3651 | continue; | ||
| 3652 | } | ||
| 3653 | MUTEX_PROF_RESET(arena->large_mtx); | ||
| 3654 | MUTEX_PROF_RESET(arena->pa_shard.edata_cache.mtx); | ||
| 3655 | MUTEX_PROF_RESET(arena->pa_shard.pac.ecache_dirty.mtx); | ||
| 3656 | MUTEX_PROF_RESET(arena->pa_shard.pac.ecache_muzzy.mtx); | ||
| 3657 | MUTEX_PROF_RESET(arena->pa_shard.pac.ecache_retained.mtx); | ||
| 3658 | MUTEX_PROF_RESET(arena->pa_shard.pac.decay_dirty.mtx); | ||
| 3659 | MUTEX_PROF_RESET(arena->pa_shard.pac.decay_muzzy.mtx); | ||
| 3660 | MUTEX_PROF_RESET(arena->tcache_ql_mtx); | ||
| 3661 | MUTEX_PROF_RESET(arena->base->mtx); | ||
| 3662 | |||
| 3663 | for (szind_t j = 0; j < SC_NBINS; j++) { | ||
| 3664 | for (unsigned k = 0; k < bin_infos[j].n_shards; k++) { | ||
| 3665 | bin_t *bin = arena_get_bin(arena, j, k); | ||
| 3666 | MUTEX_PROF_RESET(bin->lock); | ||
| 3667 | } | ||
| 3668 | } | ||
| 3669 | } | ||
| 3670 | #undef MUTEX_PROF_RESET | ||
| 3671 | return 0; | ||
| 3672 | } | ||
| 3673 | |||
| 3674 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nmalloc, | ||
| 3675 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nmalloc, uint64_t) | ||
| 3676 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_ndalloc, | ||
| 3677 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.ndalloc, uint64_t) | ||
| 3678 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nrequests, | ||
| 3679 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nrequests, uint64_t) | ||
| 3680 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curregs, | ||
| 3681 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.curregs, size_t) | ||
| 3682 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nfills, | ||
| 3683 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nfills, uint64_t) | ||
| 3684 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nflushes, | ||
| 3685 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nflushes, uint64_t) | ||
| 3686 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nslabs, | ||
| 3687 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nslabs, uint64_t) | ||
| 3688 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nreslabs, | ||
| 3689 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.reslabs, uint64_t) | ||
| 3690 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curslabs, | ||
| 3691 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.curslabs, size_t) | ||
| 3692 | CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nonfull_slabs, | ||
| 3693 | arenas_i(mib[2])->astats->bstats[mib[4]].stats_data.nonfull_slabs, size_t) | ||
| 3694 | |||
| 3695 | static const ctl_named_node_t * | ||
| 3696 | stats_arenas_i_bins_j_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3697 | size_t miblen, size_t j) { | ||
| 3698 | if (j > SC_NBINS) { | ||
| 3699 | return NULL; | ||
| 3700 | } | ||
| 3701 | return super_stats_arenas_i_bins_j_node; | ||
| 3702 | } | ||
| 3703 | |||
| 3704 | CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_nmalloc, | ||
| 3705 | locked_read_u64_unsynchronized( | ||
| 3706 | &arenas_i(mib[2])->astats->lstats[mib[4]].nmalloc), uint64_t) | ||
| 3707 | CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_ndalloc, | ||
| 3708 | locked_read_u64_unsynchronized( | ||
| 3709 | &arenas_i(mib[2])->astats->lstats[mib[4]].ndalloc), uint64_t) | ||
| 3710 | CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_nrequests, | ||
| 3711 | locked_read_u64_unsynchronized( | ||
| 3712 | &arenas_i(mib[2])->astats->lstats[mib[4]].nrequests), uint64_t) | ||
| 3713 | CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_curlextents, | ||
| 3714 | arenas_i(mib[2])->astats->lstats[mib[4]].curlextents, size_t) | ||
| 3715 | |||
| 3716 | static const ctl_named_node_t * | ||
| 3717 | stats_arenas_i_lextents_j_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3718 | size_t miblen, size_t j) { | ||
| 3719 | if (j > SC_NSIZES - SC_NBINS) { | ||
| 3720 | return NULL; | ||
| 3721 | } | ||
| 3722 | return super_stats_arenas_i_lextents_j_node; | ||
| 3723 | } | ||
| 3724 | |||
| 3725 | CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_ndirty, | ||
| 3726 | arenas_i(mib[2])->astats->estats[mib[4]].ndirty, size_t); | ||
| 3727 | CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_nmuzzy, | ||
| 3728 | arenas_i(mib[2])->astats->estats[mib[4]].nmuzzy, size_t); | ||
| 3729 | CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_nretained, | ||
| 3730 | arenas_i(mib[2])->astats->estats[mib[4]].nretained, size_t); | ||
| 3731 | CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_dirty_bytes, | ||
| 3732 | arenas_i(mib[2])->astats->estats[mib[4]].dirty_bytes, size_t); | ||
| 3733 | CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_muzzy_bytes, | ||
| 3734 | arenas_i(mib[2])->astats->estats[mib[4]].muzzy_bytes, size_t); | ||
| 3735 | CTL_RO_CGEN(config_stats, stats_arenas_i_extents_j_retained_bytes, | ||
| 3736 | arenas_i(mib[2])->astats->estats[mib[4]].retained_bytes, size_t); | ||
| 3737 | |||
| 3738 | static const ctl_named_node_t * | ||
| 3739 | stats_arenas_i_extents_j_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3740 | size_t miblen, size_t j) { | ||
| 3741 | if (j >= SC_NPSIZES) { | ||
| 3742 | return NULL; | ||
| 3743 | } | ||
| 3744 | return super_stats_arenas_i_extents_j_node; | ||
| 3745 | } | ||
| 3746 | |||
| 3747 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_npurge_passes, | ||
| 3748 | arenas_i(mib[2])->astats->hpastats.nonderived_stats.npurge_passes, uint64_t); | ||
| 3749 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_npurges, | ||
| 3750 | arenas_i(mib[2])->astats->hpastats.nonderived_stats.npurges, uint64_t); | ||
| 3751 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nhugifies, | ||
| 3752 | arenas_i(mib[2])->astats->hpastats.nonderived_stats.nhugifies, uint64_t); | ||
| 3753 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_ndehugifies, | ||
| 3754 | arenas_i(mib[2])->astats->hpastats.nonderived_stats.ndehugifies, uint64_t); | ||
| 3755 | |||
| 3756 | /* Full, nonhuge */ | ||
| 3757 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_npageslabs_nonhuge, | ||
| 3758 | arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[0].npageslabs, | ||
| 3759 | size_t); | ||
| 3760 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_nactive_nonhuge, | ||
| 3761 | arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[0].nactive, size_t); | ||
| 3762 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_ndirty_nonhuge, | ||
| 3763 | arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[0].ndirty, size_t); | ||
| 3764 | |||
| 3765 | /* Full, huge */ | ||
| 3766 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_npageslabs_huge, | ||
| 3767 | arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[1].npageslabs, | ||
| 3768 | size_t); | ||
| 3769 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_nactive_huge, | ||
| 3770 | arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[1].nactive, size_t); | ||
| 3771 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_full_slabs_ndirty_huge, | ||
| 3772 | arenas_i(mib[2])->astats->hpastats.psset_stats.full_slabs[1].ndirty, size_t); | ||
| 3773 | |||
| 3774 | /* Empty, nonhuge */ | ||
| 3775 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_npageslabs_nonhuge, | ||
| 3776 | arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[0].npageslabs, | ||
| 3777 | size_t); | ||
| 3778 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_nactive_nonhuge, | ||
| 3779 | arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[0].nactive, size_t); | ||
| 3780 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_ndirty_nonhuge, | ||
| 3781 | arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[0].ndirty, size_t); | ||
| 3782 | |||
| 3783 | /* Empty, huge */ | ||
| 3784 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_npageslabs_huge, | ||
| 3785 | arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[1].npageslabs, | ||
| 3786 | size_t); | ||
| 3787 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_nactive_huge, | ||
| 3788 | arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[1].nactive, size_t); | ||
| 3789 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_empty_slabs_ndirty_huge, | ||
| 3790 | arenas_i(mib[2])->astats->hpastats.psset_stats.empty_slabs[1].ndirty, size_t); | ||
| 3791 | |||
| 3792 | /* Nonfull, nonhuge */ | ||
| 3793 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_nonhuge, | ||
| 3794 | arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][0].npageslabs, | ||
| 3795 | size_t); | ||
| 3796 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_nonhuge, | ||
| 3797 | arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][0].nactive, | ||
| 3798 | size_t); | ||
| 3799 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_nonhuge, | ||
| 3800 | arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][0].ndirty, | ||
| 3801 | size_t); | ||
| 3802 | |||
| 3803 | /* Nonfull, huge */ | ||
| 3804 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_npageslabs_huge, | ||
| 3805 | arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][1].npageslabs, | ||
| 3806 | size_t); | ||
| 3807 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_nactive_huge, | ||
| 3808 | arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][1].nactive, | ||
| 3809 | size_t); | ||
| 3810 | CTL_RO_CGEN(config_stats, stats_arenas_i_hpa_shard_nonfull_slabs_j_ndirty_huge, | ||
| 3811 | arenas_i(mib[2])->astats->hpastats.psset_stats.nonfull_slabs[mib[5]][1].ndirty, | ||
| 3812 | size_t); | ||
| 3813 | |||
| 3814 | static const ctl_named_node_t * | ||
| 3815 | stats_arenas_i_hpa_shard_nonfull_slabs_j_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3816 | size_t miblen, size_t j) { | ||
| 3817 | if (j >= PSSET_NPSIZES) { | ||
| 3818 | return NULL; | ||
| 3819 | } | ||
| 3820 | return super_stats_arenas_i_hpa_shard_nonfull_slabs_j_node; | ||
| 3821 | } | ||
| 3822 | |||
| 3823 | static bool | ||
| 3824 | ctl_arenas_i_verify(size_t i) { | ||
| 3825 | size_t a = arenas_i2a_impl(i, true, true); | ||
| 3826 | if (a == UINT_MAX || !ctl_arenas->arenas[a]->initialized) { | ||
| 3827 | return true; | ||
| 3828 | } | ||
| 3829 | |||
| 3830 | return false; | ||
| 3831 | } | ||
| 3832 | |||
| 3833 | static const ctl_named_node_t * | ||
| 3834 | stats_arenas_i_index(tsdn_t *tsdn, const size_t *mib, | ||
| 3835 | size_t miblen, size_t i) { | ||
| 3836 | const ctl_named_node_t *ret; | ||
| 3837 | |||
| 3838 | malloc_mutex_lock(tsdn, &ctl_mtx); | ||
| 3839 | if (ctl_arenas_i_verify(i)) { | ||
| 3840 | ret = NULL; | ||
| 3841 | goto label_return; | ||
| 3842 | } | ||
| 3843 | |||
| 3844 | ret = super_stats_arenas_i_node; | ||
| 3845 | label_return: | ||
| 3846 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 3847 | return ret; | ||
| 3848 | } | ||
| 3849 | |||
| 3850 | static int | ||
| 3851 | experimental_hooks_install_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3852 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3853 | int ret; | ||
| 3854 | if (oldp == NULL || oldlenp == NULL|| newp == NULL) { | ||
| 3855 | ret = EINVAL; | ||
| 3856 | goto label_return; | ||
| 3857 | } | ||
| 3858 | /* | ||
| 3859 | * Note: this is a *private* struct. This is an experimental interface; | ||
| 3860 | * forcing the user to know the jemalloc internals well enough to | ||
| 3861 | * extract the ABI hopefully ensures nobody gets too comfortable with | ||
| 3862 | * this API, which can change at a moment's notice. | ||
| 3863 | */ | ||
| 3864 | hooks_t hooks; | ||
| 3865 | WRITE(hooks, hooks_t); | ||
| 3866 | void *handle = hook_install(tsd_tsdn(tsd), &hooks); | ||
| 3867 | if (handle == NULL) { | ||
| 3868 | ret = EAGAIN; | ||
| 3869 | goto label_return; | ||
| 3870 | } | ||
| 3871 | READ(handle, void *); | ||
| 3872 | |||
| 3873 | ret = 0; | ||
| 3874 | label_return: | ||
| 3875 | return ret; | ||
| 3876 | } | ||
| 3877 | |||
| 3878 | static int | ||
| 3879 | experimental_hooks_remove_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 3880 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3881 | int ret; | ||
| 3882 | WRITEONLY(); | ||
| 3883 | void *handle = NULL; | ||
| 3884 | WRITE(handle, void *); | ||
| 3885 | if (handle == NULL) { | ||
| 3886 | ret = EINVAL; | ||
| 3887 | goto label_return; | ||
| 3888 | } | ||
| 3889 | hook_remove(tsd_tsdn(tsd), handle); | ||
| 3890 | ret = 0; | ||
| 3891 | label_return: | ||
| 3892 | return ret; | ||
| 3893 | } | ||
| 3894 | |||
| 3895 | static int | ||
| 3896 | experimental_thread_activity_callback_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3897 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3898 | int ret; | ||
| 3899 | |||
| 3900 | if (!config_stats) { | ||
| 3901 | return ENOENT; | ||
| 3902 | } | ||
| 3903 | |||
| 3904 | activity_callback_thunk_t t_old = tsd_activity_callback_thunk_get(tsd); | ||
| 3905 | READ(t_old, activity_callback_thunk_t); | ||
| 3906 | |||
| 3907 | if (newp != NULL) { | ||
| 3908 | /* | ||
| 3909 | * This initialization is unnecessary. If it's omitted, though, | ||
| 3910 | * clang gets confused and warns on the subsequent use of t_new. | ||
| 3911 | */ | ||
| 3912 | activity_callback_thunk_t t_new = {NULL, NULL}; | ||
| 3913 | WRITE(t_new, activity_callback_thunk_t); | ||
| 3914 | tsd_activity_callback_thunk_set(tsd, t_new); | ||
| 3915 | } | ||
| 3916 | ret = 0; | ||
| 3917 | label_return: | ||
| 3918 | return ret; | ||
| 3919 | } | ||
| 3920 | |||
| 3921 | /* | ||
| 3922 | * Output six memory utilization entries for an input pointer, the first one of | ||
| 3923 | * type (void *) and the remaining five of type size_t, describing the following | ||
| 3924 | * (in the same order): | ||
| 3925 | * | ||
| 3926 | * (a) memory address of the extent a potential reallocation would go into, | ||
| 3927 | * == the five fields below describe about the extent the pointer resides in == | ||
| 3928 | * (b) number of free regions in the extent, | ||
| 3929 | * (c) number of regions in the extent, | ||
| 3930 | * (d) size of the extent in terms of bytes, | ||
| 3931 | * (e) total number of free regions in the bin the extent belongs to, and | ||
| 3932 | * (f) total number of regions in the bin the extent belongs to. | ||
| 3933 | * | ||
| 3934 | * Note that "(e)" and "(f)" are only available when stats are enabled; | ||
| 3935 | * otherwise their values are undefined. | ||
| 3936 | * | ||
| 3937 | * This API is mainly intended for small class allocations, where extents are | ||
| 3938 | * used as slab. Note that if the bin the extent belongs to is completely | ||
| 3939 | * full, "(a)" will be NULL. | ||
| 3940 | * | ||
| 3941 | * In case of large class allocations, "(a)" will be NULL, and "(e)" and "(f)" | ||
| 3942 | * will be zero (if stats are enabled; otherwise undefined). The other three | ||
| 3943 | * fields will be properly set though the values are trivial: "(b)" will be 0, | ||
| 3944 | * "(c)" will be 1, and "(d)" will be the usable size. | ||
| 3945 | * | ||
| 3946 | * The input pointer and size are respectively passed in by newp and newlen, | ||
| 3947 | * and the output fields and size are respectively oldp and *oldlenp. | ||
| 3948 | * | ||
| 3949 | * It can be beneficial to define the following macros to make it easier to | ||
| 3950 | * access the output: | ||
| 3951 | * | ||
| 3952 | * #define SLABCUR_READ(out) (*(void **)out) | ||
| 3953 | * #define COUNTS(out) ((size_t *)((void **)out + 1)) | ||
| 3954 | * #define NFREE_READ(out) COUNTS(out)[0] | ||
| 3955 | * #define NREGS_READ(out) COUNTS(out)[1] | ||
| 3956 | * #define SIZE_READ(out) COUNTS(out)[2] | ||
| 3957 | * #define BIN_NFREE_READ(out) COUNTS(out)[3] | ||
| 3958 | * #define BIN_NREGS_READ(out) COUNTS(out)[4] | ||
| 3959 | * | ||
| 3960 | * and then write e.g. NFREE_READ(oldp) to fetch the output. See the unit test | ||
| 3961 | * test_query in test/unit/extent_util.c for an example. | ||
| 3962 | * | ||
| 3963 | * For a typical defragmentation workflow making use of this API for | ||
| 3964 | * understanding the fragmentation level, please refer to the comment for | ||
| 3965 | * experimental_utilization_batch_query_ctl. | ||
| 3966 | * | ||
| 3967 | * It's up to the application how to determine the significance of | ||
| 3968 | * fragmentation relying on the outputs returned. Possible choices are: | ||
| 3969 | * | ||
| 3970 | * (a) if extent utilization ratio is below certain threshold, | ||
| 3971 | * (b) if extent memory consumption is above certain threshold, | ||
| 3972 | * (c) if extent utilization ratio is significantly below bin utilization ratio, | ||
| 3973 | * (d) if input pointer deviates a lot from potential reallocation address, or | ||
| 3974 | * (e) some selection/combination of the above. | ||
| 3975 | * | ||
| 3976 | * The caller needs to make sure that the input/output arguments are valid, | ||
| 3977 | * in particular, that the size of the output is correct, i.e.: | ||
| 3978 | * | ||
| 3979 | * *oldlenp = sizeof(void *) + sizeof(size_t) * 5 | ||
| 3980 | * | ||
| 3981 | * Otherwise, the function immediately returns EINVAL without touching anything. | ||
| 3982 | * | ||
| 3983 | * In the rare case where there's no associated extent found for the input | ||
| 3984 | * pointer, the function zeros out all output fields and return. Please refer | ||
| 3985 | * to the comment for experimental_utilization_batch_query_ctl to understand the | ||
| 3986 | * motivation from C++. | ||
| 3987 | */ | ||
| 3988 | static int | ||
| 3989 | experimental_utilization_query_ctl(tsd_t *tsd, const size_t *mib, | ||
| 3990 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 3991 | int ret; | ||
| 3992 | |||
| 3993 | assert(sizeof(inspect_extent_util_stats_verbose_t) | ||
| 3994 | == sizeof(void *) + sizeof(size_t) * 5); | ||
| 3995 | |||
| 3996 | if (oldp == NULL || oldlenp == NULL | ||
| 3997 | || *oldlenp != sizeof(inspect_extent_util_stats_verbose_t) | ||
| 3998 | || newp == NULL) { | ||
| 3999 | ret = EINVAL; | ||
| 4000 | goto label_return; | ||
| 4001 | } | ||
| 4002 | |||
| 4003 | void *ptr = NULL; | ||
| 4004 | WRITE(ptr, void *); | ||
| 4005 | inspect_extent_util_stats_verbose_t *util_stats | ||
| 4006 | = (inspect_extent_util_stats_verbose_t *)oldp; | ||
| 4007 | inspect_extent_util_stats_verbose_get(tsd_tsdn(tsd), ptr, | ||
| 4008 | &util_stats->nfree, &util_stats->nregs, &util_stats->size, | ||
| 4009 | &util_stats->bin_nfree, &util_stats->bin_nregs, | ||
| 4010 | &util_stats->slabcur_addr); | ||
| 4011 | ret = 0; | ||
| 4012 | |||
| 4013 | label_return: | ||
| 4014 | return ret; | ||
| 4015 | } | ||
| 4016 | |||
| 4017 | /* | ||
| 4018 | * Given an input array of pointers, output three memory utilization entries of | ||
| 4019 | * type size_t for each input pointer about the extent it resides in: | ||
| 4020 | * | ||
| 4021 | * (a) number of free regions in the extent, | ||
| 4022 | * (b) number of regions in the extent, and | ||
| 4023 | * (c) size of the extent in terms of bytes. | ||
| 4024 | * | ||
| 4025 | * This API is mainly intended for small class allocations, where extents are | ||
| 4026 | * used as slab. In case of large class allocations, the outputs are trivial: | ||
| 4027 | * "(a)" will be 0, "(b)" will be 1, and "(c)" will be the usable size. | ||
| 4028 | * | ||
| 4029 | * Note that multiple input pointers may reside on a same extent so the output | ||
| 4030 | * fields may contain duplicates. | ||
| 4031 | * | ||
| 4032 | * The format of the input/output looks like: | ||
| 4033 | * | ||
| 4034 | * input[0]: 1st_pointer_to_query | output[0]: 1st_extent_n_free_regions | ||
| 4035 | * | output[1]: 1st_extent_n_regions | ||
| 4036 | * | output[2]: 1st_extent_size | ||
| 4037 | * input[1]: 2nd_pointer_to_query | output[3]: 2nd_extent_n_free_regions | ||
| 4038 | * | output[4]: 2nd_extent_n_regions | ||
| 4039 | * | output[5]: 2nd_extent_size | ||
| 4040 | * ... | ... | ||
| 4041 | * | ||
| 4042 | * The input array and size are respectively passed in by newp and newlen, and | ||
| 4043 | * the output array and size are respectively oldp and *oldlenp. | ||
| 4044 | * | ||
| 4045 | * It can be beneficial to define the following macros to make it easier to | ||
| 4046 | * access the output: | ||
| 4047 | * | ||
| 4048 | * #define NFREE_READ(out, i) out[(i) * 3] | ||
| 4049 | * #define NREGS_READ(out, i) out[(i) * 3 + 1] | ||
| 4050 | * #define SIZE_READ(out, i) out[(i) * 3 + 2] | ||
| 4051 | * | ||
| 4052 | * and then write e.g. NFREE_READ(oldp, i) to fetch the output. See the unit | ||
| 4053 | * test test_batch in test/unit/extent_util.c for a concrete example. | ||
| 4054 | * | ||
| 4055 | * A typical workflow would be composed of the following steps: | ||
| 4056 | * | ||
| 4057 | * (1) flush tcache: mallctl("thread.tcache.flush", ...) | ||
| 4058 | * (2) initialize input array of pointers to query fragmentation | ||
| 4059 | * (3) allocate output array to hold utilization statistics | ||
| 4060 | * (4) query utilization: mallctl("experimental.utilization.batch_query", ...) | ||
| 4061 | * (5) (optional) decide if it's worthwhile to defragment; otherwise stop here | ||
| 4062 | * (6) disable tcache: mallctl("thread.tcache.enabled", ...) | ||
| 4063 | * (7) defragment allocations with significant fragmentation, e.g.: | ||
| 4064 | * for each allocation { | ||
| 4065 | * if it's fragmented { | ||
| 4066 | * malloc(...); | ||
| 4067 | * memcpy(...); | ||
| 4068 | * free(...); | ||
| 4069 | * } | ||
| 4070 | * } | ||
| 4071 | * (8) enable tcache: mallctl("thread.tcache.enabled", ...) | ||
| 4072 | * | ||
| 4073 | * The application can determine the significance of fragmentation themselves | ||
| 4074 | * relying on the statistics returned, both at the overall level i.e. step "(5)" | ||
| 4075 | * and at individual allocation level i.e. within step "(7)". Possible choices | ||
| 4076 | * are: | ||
| 4077 | * | ||
| 4078 | * (a) whether memory utilization ratio is below certain threshold, | ||
| 4079 | * (b) whether memory consumption is above certain threshold, or | ||
| 4080 | * (c) some combination of the two. | ||
| 4081 | * | ||
| 4082 | * The caller needs to make sure that the input/output arrays are valid and | ||
| 4083 | * their sizes are proper as well as matched, meaning: | ||
| 4084 | * | ||
| 4085 | * (a) newlen = n_pointers * sizeof(const void *) | ||
| 4086 | * (b) *oldlenp = n_pointers * sizeof(size_t) * 3 | ||
| 4087 | * (c) n_pointers > 0 | ||
| 4088 | * | ||
| 4089 | * Otherwise, the function immediately returns EINVAL without touching anything. | ||
| 4090 | * | ||
| 4091 | * In the rare case where there's no associated extent found for some pointers, | ||
| 4092 | * rather than immediately terminating the computation and raising an error, | ||
| 4093 | * the function simply zeros out the corresponding output fields and continues | ||
| 4094 | * the computation until all input pointers are handled. The motivations of | ||
| 4095 | * such a design are as follows: | ||
| 4096 | * | ||
| 4097 | * (a) The function always either processes nothing or processes everything, and | ||
| 4098 | * never leaves the output half touched and half untouched. | ||
| 4099 | * | ||
| 4100 | * (b) It facilitates usage needs especially common in C++. A vast variety of | ||
| 4101 | * C++ objects are instantiated with multiple dynamic memory allocations. For | ||
| 4102 | * example, std::string and std::vector typically use at least two allocations, | ||
| 4103 | * one for the metadata and one for the actual content. Other types may use | ||
| 4104 | * even more allocations. When inquiring about utilization statistics, the | ||
| 4105 | * caller often wants to examine into all such allocations, especially internal | ||
| 4106 | * one(s), rather than just the topmost one. The issue comes when some | ||
| 4107 | * implementations do certain optimizations to reduce/aggregate some internal | ||
| 4108 | * allocations, e.g. putting short strings directly into the metadata, and such | ||
| 4109 | * decisions are not known to the caller. Therefore, we permit pointers to | ||
| 4110 | * memory usages that may not be returned by previous malloc calls, and we | ||
| 4111 | * provide the caller a convenient way to identify such cases. | ||
| 4112 | */ | ||
| 4113 | static int | ||
| 4114 | experimental_utilization_batch_query_ctl(tsd_t *tsd, const size_t *mib, | ||
| 4115 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4116 | int ret; | ||
| 4117 | |||
| 4118 | assert(sizeof(inspect_extent_util_stats_t) == sizeof(size_t) * 3); | ||
| 4119 | |||
| 4120 | const size_t len = newlen / sizeof(const void *); | ||
| 4121 | if (oldp == NULL || oldlenp == NULL || newp == NULL || newlen == 0 | ||
| 4122 | || newlen != len * sizeof(const void *) | ||
| 4123 | || *oldlenp != len * sizeof(inspect_extent_util_stats_t)) { | ||
| 4124 | ret = EINVAL; | ||
| 4125 | goto label_return; | ||
| 4126 | } | ||
| 4127 | |||
| 4128 | void **ptrs = (void **)newp; | ||
| 4129 | inspect_extent_util_stats_t *util_stats = | ||
| 4130 | (inspect_extent_util_stats_t *)oldp; | ||
| 4131 | size_t i; | ||
| 4132 | for (i = 0; i < len; ++i) { | ||
| 4133 | inspect_extent_util_stats_get(tsd_tsdn(tsd), ptrs[i], | ||
| 4134 | &util_stats[i].nfree, &util_stats[i].nregs, | ||
| 4135 | &util_stats[i].size); | ||
| 4136 | } | ||
| 4137 | ret = 0; | ||
| 4138 | |||
| 4139 | label_return: | ||
| 4140 | return ret; | ||
| 4141 | } | ||
| 4142 | |||
| 4143 | static const ctl_named_node_t * | ||
| 4144 | experimental_arenas_i_index(tsdn_t *tsdn, const size_t *mib, | ||
| 4145 | size_t miblen, size_t i) { | ||
| 4146 | const ctl_named_node_t *ret; | ||
| 4147 | |||
| 4148 | malloc_mutex_lock(tsdn, &ctl_mtx); | ||
| 4149 | if (ctl_arenas_i_verify(i)) { | ||
| 4150 | ret = NULL; | ||
| 4151 | goto label_return; | ||
| 4152 | } | ||
| 4153 | ret = super_experimental_arenas_i_node; | ||
| 4154 | label_return: | ||
| 4155 | malloc_mutex_unlock(tsdn, &ctl_mtx); | ||
| 4156 | return ret; | ||
| 4157 | } | ||
| 4158 | |||
| 4159 | static int | ||
| 4160 | experimental_arenas_i_pactivep_ctl(tsd_t *tsd, const size_t *mib, | ||
| 4161 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4162 | if (!config_stats) { | ||
| 4163 | return ENOENT; | ||
| 4164 | } | ||
| 4165 | if (oldp == NULL || oldlenp == NULL || *oldlenp != sizeof(size_t *)) { | ||
| 4166 | return EINVAL; | ||
| 4167 | } | ||
| 4168 | |||
| 4169 | unsigned arena_ind; | ||
| 4170 | arena_t *arena; | ||
| 4171 | int ret; | ||
| 4172 | size_t *pactivep; | ||
| 4173 | |||
| 4174 | malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 4175 | READONLY(); | ||
| 4176 | MIB_UNSIGNED(arena_ind, 2); | ||
| 4177 | if (arena_ind < narenas_total_get() && (arena = | ||
| 4178 | arena_get(tsd_tsdn(tsd), arena_ind, false)) != NULL) { | ||
| 4179 | #if defined(JEMALLOC_GCC_ATOMIC_ATOMICS) || \ | ||
| 4180 | defined(JEMALLOC_GCC_SYNC_ATOMICS) || defined(_MSC_VER) | ||
| 4181 | /* Expose the underlying counter for fast read. */ | ||
| 4182 | pactivep = (size_t *)&(arena->pa_shard.nactive.repr); | ||
| 4183 | READ(pactivep, size_t *); | ||
| 4184 | ret = 0; | ||
| 4185 | #else | ||
| 4186 | ret = EFAULT; | ||
| 4187 | #endif | ||
| 4188 | } else { | ||
| 4189 | ret = EFAULT; | ||
| 4190 | } | ||
| 4191 | label_return: | ||
| 4192 | malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); | ||
| 4193 | return ret; | ||
| 4194 | } | ||
| 4195 | |||
| 4196 | static int | ||
| 4197 | experimental_prof_recent_alloc_max_ctl(tsd_t *tsd, const size_t *mib, | ||
| 4198 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4199 | int ret; | ||
| 4200 | |||
| 4201 | if (!(config_prof && opt_prof)) { | ||
| 4202 | ret = ENOENT; | ||
| 4203 | goto label_return; | ||
| 4204 | } | ||
| 4205 | |||
| 4206 | ssize_t old_max; | ||
| 4207 | if (newp != NULL) { | ||
| 4208 | if (newlen != sizeof(ssize_t)) { | ||
| 4209 | ret = EINVAL; | ||
| 4210 | goto label_return; | ||
| 4211 | } | ||
| 4212 | ssize_t max = *(ssize_t *)newp; | ||
| 4213 | if (max < -1) { | ||
| 4214 | ret = EINVAL; | ||
| 4215 | goto label_return; | ||
| 4216 | } | ||
| 4217 | old_max = prof_recent_alloc_max_ctl_write(tsd, max); | ||
| 4218 | } else { | ||
| 4219 | old_max = prof_recent_alloc_max_ctl_read(); | ||
| 4220 | } | ||
| 4221 | READ(old_max, ssize_t); | ||
| 4222 | |||
| 4223 | ret = 0; | ||
| 4224 | |||
| 4225 | label_return: | ||
| 4226 | return ret; | ||
| 4227 | } | ||
| 4228 | |||
| 4229 | typedef struct write_cb_packet_s write_cb_packet_t; | ||
| 4230 | struct write_cb_packet_s { | ||
| 4231 | write_cb_t *write_cb; | ||
| 4232 | void *cbopaque; | ||
| 4233 | }; | ||
| 4234 | |||
| 4235 | static int | ||
| 4236 | experimental_prof_recent_alloc_dump_ctl(tsd_t *tsd, const size_t *mib, | ||
| 4237 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4238 | int ret; | ||
| 4239 | |||
| 4240 | if (!(config_prof && opt_prof)) { | ||
| 4241 | ret = ENOENT; | ||
| 4242 | goto label_return; | ||
| 4243 | } | ||
| 4244 | |||
| 4245 | assert(sizeof(write_cb_packet_t) == sizeof(void *) * 2); | ||
| 4246 | |||
| 4247 | WRITEONLY(); | ||
| 4248 | write_cb_packet_t write_cb_packet; | ||
| 4249 | ASSURED_WRITE(write_cb_packet, write_cb_packet_t); | ||
| 4250 | |||
| 4251 | prof_recent_alloc_dump(tsd, write_cb_packet.write_cb, | ||
| 4252 | write_cb_packet.cbopaque); | ||
| 4253 | |||
| 4254 | ret = 0; | ||
| 4255 | |||
| 4256 | label_return: | ||
| 4257 | return ret; | ||
| 4258 | } | ||
| 4259 | |||
| 4260 | typedef struct batch_alloc_packet_s batch_alloc_packet_t; | ||
| 4261 | struct batch_alloc_packet_s { | ||
| 4262 | void **ptrs; | ||
| 4263 | size_t num; | ||
| 4264 | size_t size; | ||
| 4265 | int flags; | ||
| 4266 | }; | ||
| 4267 | |||
| 4268 | static int | ||
| 4269 | experimental_batch_alloc_ctl(tsd_t *tsd, const size_t *mib, | ||
| 4270 | size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4271 | int ret; | ||
| 4272 | |||
| 4273 | VERIFY_READ(size_t); | ||
| 4274 | |||
| 4275 | batch_alloc_packet_t batch_alloc_packet; | ||
| 4276 | ASSURED_WRITE(batch_alloc_packet, batch_alloc_packet_t); | ||
| 4277 | size_t filled = batch_alloc(batch_alloc_packet.ptrs, | ||
| 4278 | batch_alloc_packet.num, batch_alloc_packet.size, | ||
| 4279 | batch_alloc_packet.flags); | ||
| 4280 | READ(filled, size_t); | ||
| 4281 | |||
| 4282 | ret = 0; | ||
| 4283 | |||
| 4284 | label_return: | ||
| 4285 | return ret; | ||
| 4286 | } | ||
| 4287 | |||
| 4288 | static int | ||
| 4289 | prof_stats_bins_i_live_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 4290 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4291 | int ret; | ||
| 4292 | unsigned binind; | ||
| 4293 | prof_stats_t stats; | ||
| 4294 | |||
| 4295 | if (!(config_prof && opt_prof && opt_prof_stats)) { | ||
| 4296 | ret = ENOENT; | ||
| 4297 | goto label_return; | ||
| 4298 | } | ||
| 4299 | |||
| 4300 | READONLY(); | ||
| 4301 | MIB_UNSIGNED(binind, 3); | ||
| 4302 | if (binind >= SC_NBINS) { | ||
| 4303 | ret = EINVAL; | ||
| 4304 | goto label_return; | ||
| 4305 | } | ||
| 4306 | prof_stats_get_live(tsd, (szind_t)binind, &stats); | ||
| 4307 | READ(stats, prof_stats_t); | ||
| 4308 | |||
| 4309 | ret = 0; | ||
| 4310 | label_return: | ||
| 4311 | return ret; | ||
| 4312 | } | ||
| 4313 | |||
| 4314 | static int | ||
| 4315 | prof_stats_bins_i_accum_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 4316 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4317 | int ret; | ||
| 4318 | unsigned binind; | ||
| 4319 | prof_stats_t stats; | ||
| 4320 | |||
| 4321 | if (!(config_prof && opt_prof && opt_prof_stats)) { | ||
| 4322 | ret = ENOENT; | ||
| 4323 | goto label_return; | ||
| 4324 | } | ||
| 4325 | |||
| 4326 | READONLY(); | ||
| 4327 | MIB_UNSIGNED(binind, 3); | ||
| 4328 | if (binind >= SC_NBINS) { | ||
| 4329 | ret = EINVAL; | ||
| 4330 | goto label_return; | ||
| 4331 | } | ||
| 4332 | prof_stats_get_accum(tsd, (szind_t)binind, &stats); | ||
| 4333 | READ(stats, prof_stats_t); | ||
| 4334 | |||
| 4335 | ret = 0; | ||
| 4336 | label_return: | ||
| 4337 | return ret; | ||
| 4338 | } | ||
| 4339 | |||
| 4340 | static const ctl_named_node_t * | ||
| 4341 | prof_stats_bins_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, | ||
| 4342 | size_t i) { | ||
| 4343 | if (!(config_prof && opt_prof && opt_prof_stats)) { | ||
| 4344 | return NULL; | ||
| 4345 | } | ||
| 4346 | if (i >= SC_NBINS) { | ||
| 4347 | return NULL; | ||
| 4348 | } | ||
| 4349 | return super_prof_stats_bins_i_node; | ||
| 4350 | } | ||
| 4351 | |||
| 4352 | static int | ||
| 4353 | prof_stats_lextents_i_live_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 4354 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4355 | int ret; | ||
| 4356 | unsigned lextent_ind; | ||
| 4357 | prof_stats_t stats; | ||
| 4358 | |||
| 4359 | if (!(config_prof && opt_prof && opt_prof_stats)) { | ||
| 4360 | ret = ENOENT; | ||
| 4361 | goto label_return; | ||
| 4362 | } | ||
| 4363 | |||
| 4364 | READONLY(); | ||
| 4365 | MIB_UNSIGNED(lextent_ind, 3); | ||
| 4366 | if (lextent_ind >= SC_NSIZES - SC_NBINS) { | ||
| 4367 | ret = EINVAL; | ||
| 4368 | goto label_return; | ||
| 4369 | } | ||
| 4370 | prof_stats_get_live(tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); | ||
| 4371 | READ(stats, prof_stats_t); | ||
| 4372 | |||
| 4373 | ret = 0; | ||
| 4374 | label_return: | ||
| 4375 | return ret; | ||
| 4376 | } | ||
| 4377 | |||
| 4378 | static int | ||
| 4379 | prof_stats_lextents_i_accum_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, | ||
| 4380 | void *oldp, size_t *oldlenp, void *newp, size_t newlen) { | ||
| 4381 | int ret; | ||
| 4382 | unsigned lextent_ind; | ||
| 4383 | prof_stats_t stats; | ||
| 4384 | |||
| 4385 | if (!(config_prof && opt_prof && opt_prof_stats)) { | ||
| 4386 | ret = ENOENT; | ||
| 4387 | goto label_return; | ||
| 4388 | } | ||
| 4389 | |||
| 4390 | READONLY(); | ||
| 4391 | MIB_UNSIGNED(lextent_ind, 3); | ||
| 4392 | if (lextent_ind >= SC_NSIZES - SC_NBINS) { | ||
| 4393 | ret = EINVAL; | ||
| 4394 | goto label_return; | ||
| 4395 | } | ||
| 4396 | prof_stats_get_accum(tsd, (szind_t)(lextent_ind + SC_NBINS), &stats); | ||
| 4397 | READ(stats, prof_stats_t); | ||
| 4398 | |||
| 4399 | ret = 0; | ||
| 4400 | label_return: | ||
| 4401 | return ret; | ||
| 4402 | } | ||
| 4403 | |||
| 4404 | static const ctl_named_node_t * | ||
| 4405 | prof_stats_lextents_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, | ||
| 4406 | size_t i) { | ||
| 4407 | if (!(config_prof && opt_prof && opt_prof_stats)) { | ||
| 4408 | return NULL; | ||
| 4409 | } | ||
| 4410 | if (i >= SC_NSIZES - SC_NBINS) { | ||
| 4411 | return NULL; | ||
| 4412 | } | ||
| 4413 | return super_prof_stats_lextents_i_node; | ||
| 4414 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/decay.c b/examples/redis-unstable/deps/jemalloc/src/decay.c deleted file mode 100644 index d801b2b..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/decay.c +++ /dev/null | |||
| @@ -1,295 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/decay.h" | ||
| 5 | |||
| 6 | static const uint64_t h_steps[SMOOTHSTEP_NSTEPS] = { | ||
| 7 | #define STEP(step, h, x, y) \ | ||
| 8 | h, | ||
| 9 | SMOOTHSTEP | ||
| 10 | #undef STEP | ||
| 11 | }; | ||
| 12 | |||
| 13 | /* | ||
| 14 | * Generate a new deadline that is uniformly random within the next epoch after | ||
| 15 | * the current one. | ||
| 16 | */ | ||
| 17 | void | ||
| 18 | decay_deadline_init(decay_t *decay) { | ||
| 19 | nstime_copy(&decay->deadline, &decay->epoch); | ||
| 20 | nstime_add(&decay->deadline, &decay->interval); | ||
| 21 | if (decay_ms_read(decay) > 0) { | ||
| 22 | nstime_t jitter; | ||
| 23 | |||
| 24 | nstime_init(&jitter, prng_range_u64(&decay->jitter_state, | ||
| 25 | nstime_ns(&decay->interval))); | ||
| 26 | nstime_add(&decay->deadline, &jitter); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | void | ||
| 31 | decay_reinit(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms) { | ||
| 32 | atomic_store_zd(&decay->time_ms, decay_ms, ATOMIC_RELAXED); | ||
| 33 | if (decay_ms > 0) { | ||
| 34 | nstime_init(&decay->interval, (uint64_t)decay_ms * | ||
| 35 | KQU(1000000)); | ||
| 36 | nstime_idivide(&decay->interval, SMOOTHSTEP_NSTEPS); | ||
| 37 | } | ||
| 38 | |||
| 39 | nstime_copy(&decay->epoch, cur_time); | ||
| 40 | decay->jitter_state = (uint64_t)(uintptr_t)decay; | ||
| 41 | decay_deadline_init(decay); | ||
| 42 | decay->nunpurged = 0; | ||
| 43 | memset(decay->backlog, 0, SMOOTHSTEP_NSTEPS * sizeof(size_t)); | ||
| 44 | } | ||
| 45 | |||
| 46 | bool | ||
| 47 | decay_init(decay_t *decay, nstime_t *cur_time, ssize_t decay_ms) { | ||
| 48 | if (config_debug) { | ||
| 49 | for (size_t i = 0; i < sizeof(decay_t); i++) { | ||
| 50 | assert(((char *)decay)[i] == 0); | ||
| 51 | } | ||
| 52 | decay->ceil_npages = 0; | ||
| 53 | } | ||
| 54 | if (malloc_mutex_init(&decay->mtx, "decay", WITNESS_RANK_DECAY, | ||
| 55 | malloc_mutex_rank_exclusive)) { | ||
| 56 | return true; | ||
| 57 | } | ||
| 58 | decay->purging = false; | ||
| 59 | decay_reinit(decay, cur_time, decay_ms); | ||
| 60 | return false; | ||
| 61 | } | ||
| 62 | |||
| 63 | bool | ||
| 64 | decay_ms_valid(ssize_t decay_ms) { | ||
| 65 | if (decay_ms < -1) { | ||
| 66 | return false; | ||
| 67 | } | ||
| 68 | if (decay_ms == -1 || (uint64_t)decay_ms <= NSTIME_SEC_MAX * | ||
| 69 | KQU(1000)) { | ||
| 70 | return true; | ||
| 71 | } | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | |||
| 75 | static void | ||
| 76 | decay_maybe_update_time(decay_t *decay, nstime_t *new_time) { | ||
| 77 | if (unlikely(!nstime_monotonic() && nstime_compare(&decay->epoch, | ||
| 78 | new_time) > 0)) { | ||
| 79 | /* | ||
| 80 | * Time went backwards. Move the epoch back in time and | ||
| 81 | * generate a new deadline, with the expectation that time | ||
| 82 | * typically flows forward for long enough periods of time that | ||
| 83 | * epochs complete. Unfortunately, this strategy is susceptible | ||
| 84 | * to clock jitter triggering premature epoch advances, but | ||
| 85 | * clock jitter estimation and compensation isn't feasible here | ||
| 86 | * because calls into this code are event-driven. | ||
| 87 | */ | ||
| 88 | nstime_copy(&decay->epoch, new_time); | ||
| 89 | decay_deadline_init(decay); | ||
| 90 | } else { | ||
| 91 | /* Verify that time does not go backwards. */ | ||
| 92 | assert(nstime_compare(&decay->epoch, new_time) <= 0); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | static size_t | ||
| 97 | decay_backlog_npages_limit(const decay_t *decay) { | ||
| 98 | /* | ||
| 99 | * For each element of decay_backlog, multiply by the corresponding | ||
| 100 | * fixed-point smoothstep decay factor. Sum the products, then divide | ||
| 101 | * to round down to the nearest whole number of pages. | ||
| 102 | */ | ||
| 103 | uint64_t sum = 0; | ||
| 104 | for (unsigned i = 0; i < SMOOTHSTEP_NSTEPS; i++) { | ||
| 105 | sum += decay->backlog[i] * h_steps[i]; | ||
| 106 | } | ||
| 107 | size_t npages_limit_backlog = (size_t)(sum >> SMOOTHSTEP_BFP); | ||
| 108 | |||
| 109 | return npages_limit_backlog; | ||
| 110 | } | ||
| 111 | |||
| 112 | /* | ||
| 113 | * Update backlog, assuming that 'nadvance_u64' time intervals have passed. | ||
| 114 | * Trailing 'nadvance_u64' records should be erased and 'current_npages' is | ||
| 115 | * placed as the newest record. | ||
| 116 | */ | ||
| 117 | static void | ||
| 118 | decay_backlog_update(decay_t *decay, uint64_t nadvance_u64, | ||
| 119 | size_t current_npages) { | ||
| 120 | if (nadvance_u64 >= SMOOTHSTEP_NSTEPS) { | ||
| 121 | memset(decay->backlog, 0, (SMOOTHSTEP_NSTEPS-1) * | ||
| 122 | sizeof(size_t)); | ||
| 123 | } else { | ||
| 124 | size_t nadvance_z = (size_t)nadvance_u64; | ||
| 125 | |||
| 126 | assert((uint64_t)nadvance_z == nadvance_u64); | ||
| 127 | |||
| 128 | memmove(decay->backlog, &decay->backlog[nadvance_z], | ||
| 129 | (SMOOTHSTEP_NSTEPS - nadvance_z) * sizeof(size_t)); | ||
| 130 | if (nadvance_z > 1) { | ||
| 131 | memset(&decay->backlog[SMOOTHSTEP_NSTEPS - | ||
| 132 | nadvance_z], 0, (nadvance_z-1) * sizeof(size_t)); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | size_t npages_delta = (current_npages > decay->nunpurged) ? | ||
| 137 | current_npages - decay->nunpurged : 0; | ||
| 138 | decay->backlog[SMOOTHSTEP_NSTEPS-1] = npages_delta; | ||
| 139 | |||
| 140 | if (config_debug) { | ||
| 141 | if (current_npages > decay->ceil_npages) { | ||
| 142 | decay->ceil_npages = current_npages; | ||
| 143 | } | ||
| 144 | size_t npages_limit = decay_backlog_npages_limit(decay); | ||
| 145 | assert(decay->ceil_npages >= npages_limit); | ||
| 146 | if (decay->ceil_npages > npages_limit) { | ||
| 147 | decay->ceil_npages = npages_limit; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | static inline bool | ||
| 153 | decay_deadline_reached(const decay_t *decay, const nstime_t *time) { | ||
| 154 | return (nstime_compare(&decay->deadline, time) <= 0); | ||
| 155 | } | ||
| 156 | |||
| 157 | uint64_t | ||
| 158 | decay_npages_purge_in(decay_t *decay, nstime_t *time, size_t npages_new) { | ||
| 159 | uint64_t decay_interval_ns = decay_epoch_duration_ns(decay); | ||
| 160 | size_t n_epoch = (size_t)(nstime_ns(time) / decay_interval_ns); | ||
| 161 | |||
| 162 | uint64_t npages_purge; | ||
| 163 | if (n_epoch >= SMOOTHSTEP_NSTEPS) { | ||
| 164 | npages_purge = npages_new; | ||
| 165 | } else { | ||
| 166 | uint64_t h_steps_max = h_steps[SMOOTHSTEP_NSTEPS - 1]; | ||
| 167 | assert(h_steps_max >= | ||
| 168 | h_steps[SMOOTHSTEP_NSTEPS - 1 - n_epoch]); | ||
| 169 | npages_purge = npages_new * (h_steps_max - | ||
| 170 | h_steps[SMOOTHSTEP_NSTEPS - 1 - n_epoch]); | ||
| 171 | npages_purge >>= SMOOTHSTEP_BFP; | ||
| 172 | } | ||
| 173 | return npages_purge; | ||
| 174 | } | ||
| 175 | |||
| 176 | bool | ||
| 177 | decay_maybe_advance_epoch(decay_t *decay, nstime_t *new_time, | ||
| 178 | size_t npages_current) { | ||
| 179 | /* Handle possible non-monotonicity of time. */ | ||
| 180 | decay_maybe_update_time(decay, new_time); | ||
| 181 | |||
| 182 | if (!decay_deadline_reached(decay, new_time)) { | ||
| 183 | return false; | ||
| 184 | } | ||
| 185 | nstime_t delta; | ||
| 186 | nstime_copy(&delta, new_time); | ||
| 187 | nstime_subtract(&delta, &decay->epoch); | ||
| 188 | |||
| 189 | uint64_t nadvance_u64 = nstime_divide(&delta, &decay->interval); | ||
| 190 | assert(nadvance_u64 > 0); | ||
| 191 | |||
| 192 | /* Add nadvance_u64 decay intervals to epoch. */ | ||
| 193 | nstime_copy(&delta, &decay->interval); | ||
| 194 | nstime_imultiply(&delta, nadvance_u64); | ||
| 195 | nstime_add(&decay->epoch, &delta); | ||
| 196 | |||
| 197 | /* Set a new deadline. */ | ||
| 198 | decay_deadline_init(decay); | ||
| 199 | |||
| 200 | /* Update the backlog. */ | ||
| 201 | decay_backlog_update(decay, nadvance_u64, npages_current); | ||
| 202 | |||
| 203 | decay->npages_limit = decay_backlog_npages_limit(decay); | ||
| 204 | decay->nunpurged = (decay->npages_limit > npages_current) ? | ||
| 205 | decay->npages_limit : npages_current; | ||
| 206 | |||
| 207 | return true; | ||
| 208 | } | ||
| 209 | |||
| 210 | /* | ||
| 211 | * Calculate how many pages should be purged after 'interval'. | ||
| 212 | * | ||
| 213 | * First, calculate how many pages should remain at the moment, then subtract | ||
| 214 | * the number of pages that should remain after 'interval'. The difference is | ||
| 215 | * how many pages should be purged until then. | ||
| 216 | * | ||
| 217 | * The number of pages that should remain at a specific moment is calculated | ||
| 218 | * like this: pages(now) = sum(backlog[i] * h_steps[i]). After 'interval' | ||
| 219 | * passes, backlog would shift 'interval' positions to the left and sigmoid | ||
| 220 | * curve would be applied starting with backlog[interval]. | ||
| 221 | * | ||
| 222 | * The implementation doesn't directly map to the description, but it's | ||
| 223 | * essentially the same calculation, optimized to avoid iterating over | ||
| 224 | * [interval..SMOOTHSTEP_NSTEPS) twice. | ||
| 225 | */ | ||
| 226 | static inline size_t | ||
| 227 | decay_npurge_after_interval(decay_t *decay, size_t interval) { | ||
| 228 | size_t i; | ||
| 229 | uint64_t sum = 0; | ||
| 230 | for (i = 0; i < interval; i++) { | ||
| 231 | sum += decay->backlog[i] * h_steps[i]; | ||
| 232 | } | ||
| 233 | for (; i < SMOOTHSTEP_NSTEPS; i++) { | ||
| 234 | sum += decay->backlog[i] * | ||
| 235 | (h_steps[i] - h_steps[i - interval]); | ||
| 236 | } | ||
| 237 | |||
| 238 | return (size_t)(sum >> SMOOTHSTEP_BFP); | ||
| 239 | } | ||
| 240 | |||
| 241 | uint64_t decay_ns_until_purge(decay_t *decay, size_t npages_current, | ||
| 242 | uint64_t npages_threshold) { | ||
| 243 | if (!decay_gradually(decay)) { | ||
| 244 | return DECAY_UNBOUNDED_TIME_TO_PURGE; | ||
| 245 | } | ||
| 246 | uint64_t decay_interval_ns = decay_epoch_duration_ns(decay); | ||
| 247 | assert(decay_interval_ns > 0); | ||
| 248 | if (npages_current == 0) { | ||
| 249 | unsigned i; | ||
| 250 | for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) { | ||
| 251 | if (decay->backlog[i] > 0) { | ||
| 252 | break; | ||
| 253 | } | ||
| 254 | } | ||
| 255 | if (i == SMOOTHSTEP_NSTEPS) { | ||
| 256 | /* No dirty pages recorded. Sleep indefinitely. */ | ||
| 257 | return DECAY_UNBOUNDED_TIME_TO_PURGE; | ||
| 258 | } | ||
| 259 | } | ||
| 260 | if (npages_current <= npages_threshold) { | ||
| 261 | /* Use max interval. */ | ||
| 262 | return decay_interval_ns * SMOOTHSTEP_NSTEPS; | ||
| 263 | } | ||
| 264 | |||
| 265 | /* Minimal 2 intervals to ensure reaching next epoch deadline. */ | ||
| 266 | size_t lb = 2; | ||
| 267 | size_t ub = SMOOTHSTEP_NSTEPS; | ||
| 268 | |||
| 269 | size_t npurge_lb, npurge_ub; | ||
| 270 | npurge_lb = decay_npurge_after_interval(decay, lb); | ||
| 271 | if (npurge_lb > npages_threshold) { | ||
| 272 | return decay_interval_ns * lb; | ||
| 273 | } | ||
| 274 | npurge_ub = decay_npurge_after_interval(decay, ub); | ||
| 275 | if (npurge_ub < npages_threshold) { | ||
| 276 | return decay_interval_ns * ub; | ||
| 277 | } | ||
| 278 | |||
| 279 | unsigned n_search = 0; | ||
| 280 | size_t target, npurge; | ||
| 281 | while ((npurge_lb + npages_threshold < npurge_ub) && (lb + 2 < ub)) { | ||
| 282 | target = (lb + ub) / 2; | ||
| 283 | npurge = decay_npurge_after_interval(decay, target); | ||
| 284 | if (npurge > npages_threshold) { | ||
| 285 | ub = target; | ||
| 286 | npurge_ub = npurge; | ||
| 287 | } else { | ||
| 288 | lb = target; | ||
| 289 | npurge_lb = npurge; | ||
| 290 | } | ||
| 291 | assert(n_search < lg_floor(SMOOTHSTEP_NSTEPS) + 1); | ||
| 292 | ++n_search; | ||
| 293 | } | ||
| 294 | return decay_interval_ns * (ub + lb) / 2; | ||
| 295 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/div.c b/examples/redis-unstable/deps/jemalloc/src/div.c deleted file mode 100644 index 808892a..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/div.c +++ /dev/null | |||
| @@ -1,55 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | |||
| 3 | #include "jemalloc/internal/div.h" | ||
| 4 | |||
| 5 | #include "jemalloc/internal/assert.h" | ||
| 6 | |||
| 7 | /* | ||
| 8 | * Suppose we have n = q * d, all integers. We know n and d, and want q = n / d. | ||
| 9 | * | ||
| 10 | * For any k, we have (here, all division is exact; not C-style rounding): | ||
| 11 | * floor(ceil(2^k / d) * n / 2^k) = floor((2^k + r) / d * n / 2^k), where | ||
| 12 | * r = (-2^k) mod d. | ||
| 13 | * | ||
| 14 | * Expanding this out: | ||
| 15 | * ... = floor(2^k / d * n / 2^k + r / d * n / 2^k) | ||
| 16 | * = floor(n / d + (r / d) * (n / 2^k)). | ||
| 17 | * | ||
| 18 | * The fractional part of n / d is 0 (because of the assumption that d divides n | ||
| 19 | * exactly), so we have: | ||
| 20 | * ... = n / d + floor((r / d) * (n / 2^k)) | ||
| 21 | * | ||
| 22 | * So that our initial expression is equal to the quantity we seek, so long as | ||
| 23 | * (r / d) * (n / 2^k) < 1. | ||
| 24 | * | ||
| 25 | * r is a remainder mod d, so r < d and r / d < 1 always. We can make | ||
| 26 | * n / 2 ^ k < 1 by setting k = 32. This gets us a value of magic that works. | ||
| 27 | */ | ||
| 28 | |||
| 29 | void | ||
| 30 | div_init(div_info_t *div_info, size_t d) { | ||
| 31 | /* Nonsensical. */ | ||
| 32 | assert(d != 0); | ||
| 33 | /* | ||
| 34 | * This would make the value of magic too high to fit into a uint32_t | ||
| 35 | * (we would want magic = 2^32 exactly). This would mess with code gen | ||
| 36 | * on 32-bit machines. | ||
| 37 | */ | ||
| 38 | assert(d != 1); | ||
| 39 | |||
| 40 | uint64_t two_to_k = ((uint64_t)1 << 32); | ||
| 41 | uint32_t magic = (uint32_t)(two_to_k / d); | ||
| 42 | |||
| 43 | /* | ||
| 44 | * We want magic = ceil(2^k / d), but C gives us floor. We have to | ||
| 45 | * increment it unless the result was exact (i.e. unless d is a power of | ||
| 46 | * two). | ||
| 47 | */ | ||
| 48 | if (two_to_k % d != 0) { | ||
| 49 | magic++; | ||
| 50 | } | ||
| 51 | div_info->magic = magic; | ||
| 52 | #ifdef JEMALLOC_DEBUG | ||
| 53 | div_info->d = d; | ||
| 54 | #endif | ||
| 55 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/ecache.c b/examples/redis-unstable/deps/jemalloc/src/ecache.c deleted file mode 100644 index a242227..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/ecache.c +++ /dev/null | |||
| @@ -1,35 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/san.h" | ||
| 5 | |||
| 6 | bool | ||
| 7 | ecache_init(tsdn_t *tsdn, ecache_t *ecache, extent_state_t state, unsigned ind, | ||
| 8 | bool delay_coalesce) { | ||
| 9 | if (malloc_mutex_init(&ecache->mtx, "extents", WITNESS_RANK_EXTENTS, | ||
| 10 | malloc_mutex_rank_exclusive)) { | ||
| 11 | return true; | ||
| 12 | } | ||
| 13 | ecache->state = state; | ||
| 14 | ecache->ind = ind; | ||
| 15 | ecache->delay_coalesce = delay_coalesce; | ||
| 16 | eset_init(&ecache->eset, state); | ||
| 17 | eset_init(&ecache->guarded_eset, state); | ||
| 18 | |||
| 19 | return false; | ||
| 20 | } | ||
| 21 | |||
| 22 | void | ||
| 23 | ecache_prefork(tsdn_t *tsdn, ecache_t *ecache) { | ||
| 24 | malloc_mutex_prefork(tsdn, &ecache->mtx); | ||
| 25 | } | ||
| 26 | |||
| 27 | void | ||
| 28 | ecache_postfork_parent(tsdn_t *tsdn, ecache_t *ecache) { | ||
| 29 | malloc_mutex_postfork_parent(tsdn, &ecache->mtx); | ||
| 30 | } | ||
| 31 | |||
| 32 | void | ||
| 33 | ecache_postfork_child(tsdn_t *tsdn, ecache_t *ecache) { | ||
| 34 | malloc_mutex_postfork_child(tsdn, &ecache->mtx); | ||
| 35 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/edata.c b/examples/redis-unstable/deps/jemalloc/src/edata.c deleted file mode 100644 index 82b6f56..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/edata.c +++ /dev/null | |||
| @@ -1,6 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | ph_gen(, edata_avail, edata_t, avail_link, | ||
| 5 | edata_esnead_comp) | ||
| 6 | ph_gen(, edata_heap, edata_t, heap_link, edata_snad_comp) | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/edata_cache.c b/examples/redis-unstable/deps/jemalloc/src/edata_cache.c deleted file mode 100644 index 6bc1848..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/edata_cache.c +++ /dev/null | |||
| @@ -1,154 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | bool | ||
| 5 | edata_cache_init(edata_cache_t *edata_cache, base_t *base) { | ||
| 6 | edata_avail_new(&edata_cache->avail); | ||
| 7 | /* | ||
| 8 | * This is not strictly necessary, since the edata_cache_t is only | ||
| 9 | * created inside an arena, which is zeroed on creation. But this is | ||
| 10 | * handy as a safety measure. | ||
| 11 | */ | ||
| 12 | atomic_store_zu(&edata_cache->count, 0, ATOMIC_RELAXED); | ||
| 13 | if (malloc_mutex_init(&edata_cache->mtx, "edata_cache", | ||
| 14 | WITNESS_RANK_EDATA_CACHE, malloc_mutex_rank_exclusive)) { | ||
| 15 | return true; | ||
| 16 | } | ||
| 17 | edata_cache->base = base; | ||
| 18 | return false; | ||
| 19 | } | ||
| 20 | |||
| 21 | edata_t * | ||
| 22 | edata_cache_get(tsdn_t *tsdn, edata_cache_t *edata_cache) { | ||
| 23 | malloc_mutex_lock(tsdn, &edata_cache->mtx); | ||
| 24 | edata_t *edata = edata_avail_first(&edata_cache->avail); | ||
| 25 | if (edata == NULL) { | ||
| 26 | malloc_mutex_unlock(tsdn, &edata_cache->mtx); | ||
| 27 | return base_alloc_edata(tsdn, edata_cache->base); | ||
| 28 | } | ||
| 29 | edata_avail_remove(&edata_cache->avail, edata); | ||
| 30 | atomic_load_sub_store_zu(&edata_cache->count, 1); | ||
| 31 | malloc_mutex_unlock(tsdn, &edata_cache->mtx); | ||
| 32 | return edata; | ||
| 33 | } | ||
| 34 | |||
| 35 | void | ||
| 36 | edata_cache_put(tsdn_t *tsdn, edata_cache_t *edata_cache, edata_t *edata) { | ||
| 37 | malloc_mutex_lock(tsdn, &edata_cache->mtx); | ||
| 38 | edata_avail_insert(&edata_cache->avail, edata); | ||
| 39 | atomic_load_add_store_zu(&edata_cache->count, 1); | ||
| 40 | malloc_mutex_unlock(tsdn, &edata_cache->mtx); | ||
| 41 | } | ||
| 42 | |||
| 43 | void | ||
| 44 | edata_cache_prefork(tsdn_t *tsdn, edata_cache_t *edata_cache) { | ||
| 45 | malloc_mutex_prefork(tsdn, &edata_cache->mtx); | ||
| 46 | } | ||
| 47 | |||
| 48 | void | ||
| 49 | edata_cache_postfork_parent(tsdn_t *tsdn, edata_cache_t *edata_cache) { | ||
| 50 | malloc_mutex_postfork_parent(tsdn, &edata_cache->mtx); | ||
| 51 | } | ||
| 52 | |||
| 53 | void | ||
| 54 | edata_cache_postfork_child(tsdn_t *tsdn, edata_cache_t *edata_cache) { | ||
| 55 | malloc_mutex_postfork_child(tsdn, &edata_cache->mtx); | ||
| 56 | } | ||
| 57 | |||
| 58 | void | ||
| 59 | edata_cache_fast_init(edata_cache_fast_t *ecs, edata_cache_t *fallback) { | ||
| 60 | edata_list_inactive_init(&ecs->list); | ||
| 61 | ecs->fallback = fallback; | ||
| 62 | ecs->disabled = false; | ||
| 63 | } | ||
| 64 | |||
| 65 | static void | ||
| 66 | edata_cache_fast_try_fill_from_fallback(tsdn_t *tsdn, | ||
| 67 | edata_cache_fast_t *ecs) { | ||
| 68 | edata_t *edata; | ||
| 69 | malloc_mutex_lock(tsdn, &ecs->fallback->mtx); | ||
| 70 | for (int i = 0; i < EDATA_CACHE_FAST_FILL; i++) { | ||
| 71 | edata = edata_avail_remove_first(&ecs->fallback->avail); | ||
| 72 | if (edata == NULL) { | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | edata_list_inactive_append(&ecs->list, edata); | ||
| 76 | atomic_load_sub_store_zu(&ecs->fallback->count, 1); | ||
| 77 | } | ||
| 78 | malloc_mutex_unlock(tsdn, &ecs->fallback->mtx); | ||
| 79 | } | ||
| 80 | |||
| 81 | edata_t * | ||
| 82 | edata_cache_fast_get(tsdn_t *tsdn, edata_cache_fast_t *ecs) { | ||
| 83 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 84 | WITNESS_RANK_EDATA_CACHE, 0); | ||
| 85 | |||
| 86 | if (ecs->disabled) { | ||
| 87 | assert(edata_list_inactive_first(&ecs->list) == NULL); | ||
| 88 | return edata_cache_get(tsdn, ecs->fallback); | ||
| 89 | } | ||
| 90 | |||
| 91 | edata_t *edata = edata_list_inactive_first(&ecs->list); | ||
| 92 | if (edata != NULL) { | ||
| 93 | edata_list_inactive_remove(&ecs->list, edata); | ||
| 94 | return edata; | ||
| 95 | } | ||
| 96 | /* Slow path; requires synchronization. */ | ||
| 97 | edata_cache_fast_try_fill_from_fallback(tsdn, ecs); | ||
| 98 | edata = edata_list_inactive_first(&ecs->list); | ||
| 99 | if (edata != NULL) { | ||
| 100 | edata_list_inactive_remove(&ecs->list, edata); | ||
| 101 | } else { | ||
| 102 | /* | ||
| 103 | * Slowest path (fallback was also empty); allocate something | ||
| 104 | * new. | ||
| 105 | */ | ||
| 106 | edata = base_alloc_edata(tsdn, ecs->fallback->base); | ||
| 107 | } | ||
| 108 | return edata; | ||
| 109 | } | ||
| 110 | |||
| 111 | static void | ||
| 112 | edata_cache_fast_flush_all(tsdn_t *tsdn, edata_cache_fast_t *ecs) { | ||
| 113 | /* | ||
| 114 | * You could imagine smarter cache management policies (like | ||
| 115 | * only flushing down to some threshold in anticipation of | ||
| 116 | * future get requests). But just flushing everything provides | ||
| 117 | * a good opportunity to defrag too, and lets us share code between the | ||
| 118 | * flush and disable pathways. | ||
| 119 | */ | ||
| 120 | edata_t *edata; | ||
| 121 | size_t nflushed = 0; | ||
| 122 | malloc_mutex_lock(tsdn, &ecs->fallback->mtx); | ||
| 123 | while ((edata = edata_list_inactive_first(&ecs->list)) != NULL) { | ||
| 124 | edata_list_inactive_remove(&ecs->list, edata); | ||
| 125 | edata_avail_insert(&ecs->fallback->avail, edata); | ||
| 126 | nflushed++; | ||
| 127 | } | ||
| 128 | atomic_load_add_store_zu(&ecs->fallback->count, nflushed); | ||
| 129 | malloc_mutex_unlock(tsdn, &ecs->fallback->mtx); | ||
| 130 | } | ||
| 131 | |||
| 132 | void | ||
| 133 | edata_cache_fast_put(tsdn_t *tsdn, edata_cache_fast_t *ecs, edata_t *edata) { | ||
| 134 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 135 | WITNESS_RANK_EDATA_CACHE, 0); | ||
| 136 | |||
| 137 | if (ecs->disabled) { | ||
| 138 | assert(edata_list_inactive_first(&ecs->list) == NULL); | ||
| 139 | edata_cache_put(tsdn, ecs->fallback, edata); | ||
| 140 | return; | ||
| 141 | } | ||
| 142 | |||
| 143 | /* | ||
| 144 | * Prepend rather than append, to do LIFO ordering in the hopes of some | ||
| 145 | * cache locality. | ||
| 146 | */ | ||
| 147 | edata_list_inactive_prepend(&ecs->list, edata); | ||
| 148 | } | ||
| 149 | |||
| 150 | void | ||
| 151 | edata_cache_fast_disable(tsdn_t *tsdn, edata_cache_fast_t *ecs) { | ||
| 152 | edata_cache_fast_flush_all(tsdn, ecs); | ||
| 153 | ecs->disabled = true; | ||
| 154 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/ehooks.c b/examples/redis-unstable/deps/jemalloc/src/ehooks.c deleted file mode 100644 index 383e9de..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/ehooks.c +++ /dev/null | |||
| @@ -1,275 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/ehooks.h" | ||
| 5 | #include "jemalloc/internal/extent_mmap.h" | ||
| 6 | |||
| 7 | void | ||
| 8 | ehooks_init(ehooks_t *ehooks, extent_hooks_t *extent_hooks, unsigned ind) { | ||
| 9 | /* All other hooks are optional; this one is not. */ | ||
| 10 | assert(extent_hooks->alloc != NULL); | ||
| 11 | ehooks->ind = ind; | ||
| 12 | ehooks_set_extent_hooks_ptr(ehooks, extent_hooks); | ||
| 13 | } | ||
| 14 | |||
| 15 | /* | ||
| 16 | * If the caller specifies (!*zero), it is still possible to receive zeroed | ||
| 17 | * memory, in which case *zero is toggled to true. arena_extent_alloc() takes | ||
| 18 | * advantage of this to avoid demanding zeroed extents, but taking advantage of | ||
| 19 | * them if they are returned. | ||
| 20 | */ | ||
| 21 | static void * | ||
| 22 | extent_alloc_core(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size, | ||
| 23 | size_t alignment, bool *zero, bool *commit, dss_prec_t dss_prec) { | ||
| 24 | void *ret; | ||
| 25 | |||
| 26 | assert(size != 0); | ||
| 27 | assert(alignment != 0); | ||
| 28 | |||
| 29 | /* "primary" dss. */ | ||
| 30 | if (have_dss && dss_prec == dss_prec_primary && (ret = | ||
| 31 | extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero, | ||
| 32 | commit)) != NULL) { | ||
| 33 | return ret; | ||
| 34 | } | ||
| 35 | /* mmap. */ | ||
| 36 | if ((ret = extent_alloc_mmap(new_addr, size, alignment, zero, commit)) | ||
| 37 | != NULL) { | ||
| 38 | return ret; | ||
| 39 | } | ||
| 40 | /* "secondary" dss. */ | ||
| 41 | if (have_dss && dss_prec == dss_prec_secondary && (ret = | ||
| 42 | extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero, | ||
| 43 | commit)) != NULL) { | ||
| 44 | return ret; | ||
| 45 | } | ||
| 46 | |||
| 47 | /* All strategies for allocation failed. */ | ||
| 48 | return NULL; | ||
| 49 | } | ||
| 50 | |||
| 51 | void * | ||
| 52 | ehooks_default_alloc_impl(tsdn_t *tsdn, void *new_addr, size_t size, | ||
| 53 | size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { | ||
| 54 | arena_t *arena = arena_get(tsdn, arena_ind, false); | ||
| 55 | /* NULL arena indicates arena_create. */ | ||
| 56 | assert(arena != NULL || alignment == HUGEPAGE); | ||
| 57 | dss_prec_t dss = (arena == NULL) ? dss_prec_disabled : | ||
| 58 | (dss_prec_t)atomic_load_u(&arena->dss_prec, ATOMIC_RELAXED); | ||
| 59 | void *ret = extent_alloc_core(tsdn, arena, new_addr, size, alignment, | ||
| 60 | zero, commit, dss); | ||
| 61 | if (have_madvise_huge && ret) { | ||
| 62 | pages_set_thp_state(ret, size); | ||
| 63 | } | ||
| 64 | return ret; | ||
| 65 | } | ||
| 66 | |||
| 67 | static void * | ||
| 68 | ehooks_default_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size, | ||
| 69 | size_t alignment, bool *zero, bool *commit, unsigned arena_ind) { | ||
| 70 | return ehooks_default_alloc_impl(tsdn_fetch(), new_addr, size, | ||
| 71 | ALIGNMENT_CEILING(alignment, PAGE), zero, commit, arena_ind); | ||
| 72 | } | ||
| 73 | |||
| 74 | bool | ||
| 75 | ehooks_default_dalloc_impl(void *addr, size_t size) { | ||
| 76 | if (!have_dss || !extent_in_dss(addr)) { | ||
| 77 | return extent_dalloc_mmap(addr, size); | ||
| 78 | } | ||
| 79 | return true; | ||
| 80 | } | ||
| 81 | |||
| 82 | static bool | ||
| 83 | ehooks_default_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size, | ||
| 84 | bool committed, unsigned arena_ind) { | ||
| 85 | return ehooks_default_dalloc_impl(addr, size); | ||
| 86 | } | ||
| 87 | |||
| 88 | void | ||
| 89 | ehooks_default_destroy_impl(void *addr, size_t size) { | ||
| 90 | if (!have_dss || !extent_in_dss(addr)) { | ||
| 91 | pages_unmap(addr, size); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | static void | ||
| 96 | ehooks_default_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size, | ||
| 97 | bool committed, unsigned arena_ind) { | ||
| 98 | ehooks_default_destroy_impl(addr, size); | ||
| 99 | } | ||
| 100 | |||
| 101 | bool | ||
| 102 | ehooks_default_commit_impl(void *addr, size_t offset, size_t length) { | ||
| 103 | return pages_commit((void *)((uintptr_t)addr + (uintptr_t)offset), | ||
| 104 | length); | ||
| 105 | } | ||
| 106 | |||
| 107 | static bool | ||
| 108 | ehooks_default_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, | ||
| 109 | size_t offset, size_t length, unsigned arena_ind) { | ||
| 110 | return ehooks_default_commit_impl(addr, offset, length); | ||
| 111 | } | ||
| 112 | |||
| 113 | bool | ||
| 114 | ehooks_default_decommit_impl(void *addr, size_t offset, size_t length) { | ||
| 115 | return pages_decommit((void *)((uintptr_t)addr + (uintptr_t)offset), | ||
| 116 | length); | ||
| 117 | } | ||
| 118 | |||
| 119 | static bool | ||
| 120 | ehooks_default_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size, | ||
| 121 | size_t offset, size_t length, unsigned arena_ind) { | ||
| 122 | return ehooks_default_decommit_impl(addr, offset, length); | ||
| 123 | } | ||
| 124 | |||
| 125 | #ifdef PAGES_CAN_PURGE_LAZY | ||
| 126 | bool | ||
| 127 | ehooks_default_purge_lazy_impl(void *addr, size_t offset, size_t length) { | ||
| 128 | return pages_purge_lazy((void *)((uintptr_t)addr + (uintptr_t)offset), | ||
| 129 | length); | ||
| 130 | } | ||
| 131 | |||
| 132 | static bool | ||
| 133 | ehooks_default_purge_lazy(extent_hooks_t *extent_hooks, void *addr, size_t size, | ||
| 134 | size_t offset, size_t length, unsigned arena_ind) { | ||
| 135 | assert(addr != NULL); | ||
| 136 | assert((offset & PAGE_MASK) == 0); | ||
| 137 | assert(length != 0); | ||
| 138 | assert((length & PAGE_MASK) == 0); | ||
| 139 | return ehooks_default_purge_lazy_impl(addr, offset, length); | ||
| 140 | } | ||
| 141 | #endif | ||
| 142 | |||
| 143 | #ifdef PAGES_CAN_PURGE_FORCED | ||
| 144 | bool | ||
| 145 | ehooks_default_purge_forced_impl(void *addr, size_t offset, size_t length) { | ||
| 146 | return pages_purge_forced((void *)((uintptr_t)addr + | ||
| 147 | (uintptr_t)offset), length); | ||
| 148 | } | ||
| 149 | |||
| 150 | static bool | ||
| 151 | ehooks_default_purge_forced(extent_hooks_t *extent_hooks, void *addr, | ||
| 152 | size_t size, size_t offset, size_t length, unsigned arena_ind) { | ||
| 153 | assert(addr != NULL); | ||
| 154 | assert((offset & PAGE_MASK) == 0); | ||
| 155 | assert(length != 0); | ||
| 156 | assert((length & PAGE_MASK) == 0); | ||
| 157 | return ehooks_default_purge_forced_impl(addr, offset, length); | ||
| 158 | } | ||
| 159 | #endif | ||
| 160 | |||
| 161 | bool | ||
| 162 | ehooks_default_split_impl() { | ||
| 163 | if (!maps_coalesce) { | ||
| 164 | /* | ||
| 165 | * Without retain, only whole regions can be purged (required by | ||
| 166 | * MEM_RELEASE on Windows) -- therefore disallow splitting. See | ||
| 167 | * comments in extent_head_no_merge(). | ||
| 168 | */ | ||
| 169 | return !opt_retain; | ||
| 170 | } | ||
| 171 | |||
| 172 | return false; | ||
| 173 | } | ||
| 174 | |||
| 175 | static bool | ||
| 176 | ehooks_default_split(extent_hooks_t *extent_hooks, void *addr, size_t size, | ||
| 177 | size_t size_a, size_t size_b, bool committed, unsigned arena_ind) { | ||
| 178 | return ehooks_default_split_impl(); | ||
| 179 | } | ||
| 180 | |||
| 181 | bool | ||
| 182 | ehooks_default_merge_impl(tsdn_t *tsdn, void *addr_a, void *addr_b) { | ||
| 183 | assert(addr_a < addr_b); | ||
| 184 | /* | ||
| 185 | * For non-DSS cases -- | ||
| 186 | * a) W/o maps_coalesce, merge is not always allowed (Windows): | ||
| 187 | * 1) w/o retain, never merge (first branch below). | ||
| 188 | * 2) with retain, only merge extents from the same VirtualAlloc | ||
| 189 | * region (in which case MEM_DECOMMIT is utilized for purging). | ||
| 190 | * | ||
| 191 | * b) With maps_coalesce, it's always possible to merge. | ||
| 192 | * 1) w/o retain, always allow merge (only about dirty / muzzy). | ||
| 193 | * 2) with retain, to preserve the SN / first-fit, merge is still | ||
| 194 | * disallowed if b is a head extent, i.e. no merging across | ||
| 195 | * different mmap regions. | ||
| 196 | * | ||
| 197 | * a2) and b2) are implemented in emap_try_acquire_edata_neighbor, and | ||
| 198 | * sanity checked in the second branch below. | ||
| 199 | */ | ||
| 200 | if (!maps_coalesce && !opt_retain) { | ||
| 201 | return true; | ||
| 202 | } | ||
| 203 | if (config_debug) { | ||
| 204 | edata_t *a = emap_edata_lookup(tsdn, &arena_emap_global, | ||
| 205 | addr_a); | ||
| 206 | bool head_a = edata_is_head_get(a); | ||
| 207 | edata_t *b = emap_edata_lookup(tsdn, &arena_emap_global, | ||
| 208 | addr_b); | ||
| 209 | bool head_b = edata_is_head_get(b); | ||
| 210 | emap_assert_mapped(tsdn, &arena_emap_global, a); | ||
| 211 | emap_assert_mapped(tsdn, &arena_emap_global, b); | ||
| 212 | assert(extent_neighbor_head_state_mergeable(head_a, head_b, | ||
| 213 | /* forward */ true)); | ||
| 214 | } | ||
| 215 | if (have_dss && !extent_dss_mergeable(addr_a, addr_b)) { | ||
| 216 | return true; | ||
| 217 | } | ||
| 218 | |||
| 219 | return false; | ||
| 220 | } | ||
| 221 | |||
| 222 | bool | ||
| 223 | ehooks_default_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, | ||
| 224 | void *addr_b, size_t size_b, bool committed, unsigned arena_ind) { | ||
| 225 | tsdn_t *tsdn = tsdn_fetch(); | ||
| 226 | |||
| 227 | return ehooks_default_merge_impl(tsdn, addr_a, addr_b); | ||
| 228 | } | ||
| 229 | |||
| 230 | void | ||
| 231 | ehooks_default_zero_impl(void *addr, size_t size) { | ||
| 232 | /* | ||
| 233 | * By default, we try to zero out memory using OS-provided demand-zeroed | ||
| 234 | * pages. If the user has specifically requested hugepages, though, we | ||
| 235 | * don't want to purge in the middle of a hugepage (which would break it | ||
| 236 | * up), so we act conservatively and use memset. | ||
| 237 | */ | ||
| 238 | bool needs_memset = true; | ||
| 239 | if (opt_thp != thp_mode_always) { | ||
| 240 | needs_memset = pages_purge_forced(addr, size); | ||
| 241 | } | ||
| 242 | if (needs_memset) { | ||
| 243 | memset(addr, 0, size); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | void | ||
| 248 | ehooks_default_guard_impl(void *guard1, void *guard2) { | ||
| 249 | pages_mark_guards(guard1, guard2); | ||
| 250 | } | ||
| 251 | |||
| 252 | void | ||
| 253 | ehooks_default_unguard_impl(void *guard1, void *guard2) { | ||
| 254 | pages_unmark_guards(guard1, guard2); | ||
| 255 | } | ||
| 256 | |||
| 257 | const extent_hooks_t ehooks_default_extent_hooks = { | ||
| 258 | ehooks_default_alloc, | ||
| 259 | ehooks_default_dalloc, | ||
| 260 | ehooks_default_destroy, | ||
| 261 | ehooks_default_commit, | ||
| 262 | ehooks_default_decommit, | ||
| 263 | #ifdef PAGES_CAN_PURGE_LAZY | ||
| 264 | ehooks_default_purge_lazy, | ||
| 265 | #else | ||
| 266 | NULL, | ||
| 267 | #endif | ||
| 268 | #ifdef PAGES_CAN_PURGE_FORCED | ||
| 269 | ehooks_default_purge_forced, | ||
| 270 | #else | ||
| 271 | NULL, | ||
| 272 | #endif | ||
| 273 | ehooks_default_split, | ||
| 274 | ehooks_default_merge | ||
| 275 | }; | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/emap.c b/examples/redis-unstable/deps/jemalloc/src/emap.c deleted file mode 100644 index 9cc95a7..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/emap.c +++ /dev/null | |||
| @@ -1,386 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/emap.h" | ||
| 5 | |||
| 6 | enum emap_lock_result_e { | ||
| 7 | emap_lock_result_success, | ||
| 8 | emap_lock_result_failure, | ||
| 9 | emap_lock_result_no_extent | ||
| 10 | }; | ||
| 11 | typedef enum emap_lock_result_e emap_lock_result_t; | ||
| 12 | |||
| 13 | bool | ||
| 14 | emap_init(emap_t *emap, base_t *base, bool zeroed) { | ||
| 15 | return rtree_new(&emap->rtree, base, zeroed); | ||
| 16 | } | ||
| 17 | |||
| 18 | void | ||
| 19 | emap_update_edata_state(tsdn_t *tsdn, emap_t *emap, edata_t *edata, | ||
| 20 | extent_state_t state) { | ||
| 21 | witness_assert_positive_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 22 | WITNESS_RANK_CORE); | ||
| 23 | |||
| 24 | edata_state_set(edata, state); | ||
| 25 | |||
| 26 | EMAP_DECLARE_RTREE_CTX; | ||
| 27 | rtree_leaf_elm_t *elm1 = rtree_leaf_elm_lookup(tsdn, &emap->rtree, | ||
| 28 | rtree_ctx, (uintptr_t)edata_base_get(edata), /* dependent */ true, | ||
| 29 | /* init_missing */ false); | ||
| 30 | assert(elm1 != NULL); | ||
| 31 | rtree_leaf_elm_t *elm2 = edata_size_get(edata) == PAGE ? NULL : | ||
| 32 | rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, | ||
| 33 | (uintptr_t)edata_last_get(edata), /* dependent */ true, | ||
| 34 | /* init_missing */ false); | ||
| 35 | |||
| 36 | rtree_leaf_elm_state_update(tsdn, &emap->rtree, elm1, elm2, state); | ||
| 37 | |||
| 38 | emap_assert_mapped(tsdn, emap, edata); | ||
| 39 | } | ||
| 40 | |||
| 41 | static inline edata_t * | ||
| 42 | emap_try_acquire_edata_neighbor_impl(tsdn_t *tsdn, emap_t *emap, edata_t *edata, | ||
| 43 | extent_pai_t pai, extent_state_t expected_state, bool forward, | ||
| 44 | bool expanding) { | ||
| 45 | witness_assert_positive_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 46 | WITNESS_RANK_CORE); | ||
| 47 | assert(!edata_guarded_get(edata)); | ||
| 48 | assert(!expanding || forward); | ||
| 49 | assert(!edata_state_in_transition(expected_state)); | ||
| 50 | assert(expected_state == extent_state_dirty || | ||
| 51 | expected_state == extent_state_muzzy || | ||
| 52 | expected_state == extent_state_retained); | ||
| 53 | |||
| 54 | void *neighbor_addr = forward ? edata_past_get(edata) : | ||
| 55 | edata_before_get(edata); | ||
| 56 | /* | ||
| 57 | * This is subtle; the rtree code asserts that its input pointer is | ||
| 58 | * non-NULL, and this is a useful thing to check. But it's possible | ||
| 59 | * that edata corresponds to an address of (void *)PAGE (in practice, | ||
| 60 | * this has only been observed on FreeBSD when address-space | ||
| 61 | * randomization is on, but it could in principle happen anywhere). In | ||
| 62 | * this case, edata_before_get(edata) is NULL, triggering the assert. | ||
| 63 | */ | ||
| 64 | if (neighbor_addr == NULL) { | ||
| 65 | return NULL; | ||
| 66 | } | ||
| 67 | |||
| 68 | EMAP_DECLARE_RTREE_CTX; | ||
| 69 | rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, &emap->rtree, | ||
| 70 | rtree_ctx, (uintptr_t)neighbor_addr, /* dependent*/ false, | ||
| 71 | /* init_missing */ false); | ||
| 72 | if (elm == NULL) { | ||
| 73 | return NULL; | ||
| 74 | } | ||
| 75 | |||
| 76 | rtree_contents_t neighbor_contents = rtree_leaf_elm_read(tsdn, | ||
| 77 | &emap->rtree, elm, /* dependent */ true); | ||
| 78 | if (!extent_can_acquire_neighbor(edata, neighbor_contents, pai, | ||
| 79 | expected_state, forward, expanding)) { | ||
| 80 | return NULL; | ||
| 81 | } | ||
| 82 | |||
| 83 | /* From this point, the neighbor edata can be safely acquired. */ | ||
| 84 | edata_t *neighbor = neighbor_contents.edata; | ||
| 85 | assert(edata_state_get(neighbor) == expected_state); | ||
| 86 | emap_update_edata_state(tsdn, emap, neighbor, extent_state_merging); | ||
| 87 | if (expanding) { | ||
| 88 | extent_assert_can_expand(edata, neighbor); | ||
| 89 | } else { | ||
| 90 | extent_assert_can_coalesce(edata, neighbor); | ||
| 91 | } | ||
| 92 | |||
| 93 | return neighbor; | ||
| 94 | } | ||
| 95 | |||
| 96 | edata_t * | ||
| 97 | emap_try_acquire_edata_neighbor(tsdn_t *tsdn, emap_t *emap, edata_t *edata, | ||
| 98 | extent_pai_t pai, extent_state_t expected_state, bool forward) { | ||
| 99 | return emap_try_acquire_edata_neighbor_impl(tsdn, emap, edata, pai, | ||
| 100 | expected_state, forward, /* expand */ false); | ||
| 101 | } | ||
| 102 | |||
| 103 | edata_t * | ||
| 104 | emap_try_acquire_edata_neighbor_expand(tsdn_t *tsdn, emap_t *emap, | ||
| 105 | edata_t *edata, extent_pai_t pai, extent_state_t expected_state) { | ||
| 106 | /* Try expanding forward. */ | ||
| 107 | return emap_try_acquire_edata_neighbor_impl(tsdn, emap, edata, pai, | ||
| 108 | expected_state, /* forward */ true, /* expand */ true); | ||
| 109 | } | ||
| 110 | |||
| 111 | void | ||
| 112 | emap_release_edata(tsdn_t *tsdn, emap_t *emap, edata_t *edata, | ||
| 113 | extent_state_t new_state) { | ||
| 114 | assert(emap_edata_in_transition(tsdn, emap, edata)); | ||
| 115 | assert(emap_edata_is_acquired(tsdn, emap, edata)); | ||
| 116 | |||
| 117 | emap_update_edata_state(tsdn, emap, edata, new_state); | ||
| 118 | } | ||
| 119 | |||
| 120 | static bool | ||
| 121 | emap_rtree_leaf_elms_lookup(tsdn_t *tsdn, emap_t *emap, rtree_ctx_t *rtree_ctx, | ||
| 122 | const edata_t *edata, bool dependent, bool init_missing, | ||
| 123 | rtree_leaf_elm_t **r_elm_a, rtree_leaf_elm_t **r_elm_b) { | ||
| 124 | *r_elm_a = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, | ||
| 125 | (uintptr_t)edata_base_get(edata), dependent, init_missing); | ||
| 126 | if (!dependent && *r_elm_a == NULL) { | ||
| 127 | return true; | ||
| 128 | } | ||
| 129 | assert(*r_elm_a != NULL); | ||
| 130 | |||
| 131 | *r_elm_b = rtree_leaf_elm_lookup(tsdn, &emap->rtree, rtree_ctx, | ||
| 132 | (uintptr_t)edata_last_get(edata), dependent, init_missing); | ||
| 133 | if (!dependent && *r_elm_b == NULL) { | ||
| 134 | return true; | ||
| 135 | } | ||
| 136 | assert(*r_elm_b != NULL); | ||
| 137 | |||
| 138 | return false; | ||
| 139 | } | ||
| 140 | |||
| 141 | static void | ||
| 142 | emap_rtree_write_acquired(tsdn_t *tsdn, emap_t *emap, rtree_leaf_elm_t *elm_a, | ||
| 143 | rtree_leaf_elm_t *elm_b, edata_t *edata, szind_t szind, bool slab) { | ||
| 144 | rtree_contents_t contents; | ||
| 145 | contents.edata = edata; | ||
| 146 | contents.metadata.szind = szind; | ||
| 147 | contents.metadata.slab = slab; | ||
| 148 | contents.metadata.is_head = (edata == NULL) ? false : | ||
| 149 | edata_is_head_get(edata); | ||
| 150 | contents.metadata.state = (edata == NULL) ? 0 : edata_state_get(edata); | ||
| 151 | rtree_leaf_elm_write(tsdn, &emap->rtree, elm_a, contents); | ||
| 152 | if (elm_b != NULL) { | ||
| 153 | rtree_leaf_elm_write(tsdn, &emap->rtree, elm_b, contents); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | bool | ||
| 158 | emap_register_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata, | ||
| 159 | szind_t szind, bool slab) { | ||
| 160 | assert(edata_state_get(edata) == extent_state_active); | ||
| 161 | EMAP_DECLARE_RTREE_CTX; | ||
| 162 | |||
| 163 | rtree_leaf_elm_t *elm_a, *elm_b; | ||
| 164 | bool err = emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, edata, | ||
| 165 | false, true, &elm_a, &elm_b); | ||
| 166 | if (err) { | ||
| 167 | return true; | ||
| 168 | } | ||
| 169 | assert(rtree_leaf_elm_read(tsdn, &emap->rtree, elm_a, | ||
| 170 | /* dependent */ false).edata == NULL); | ||
| 171 | assert(rtree_leaf_elm_read(tsdn, &emap->rtree, elm_b, | ||
| 172 | /* dependent */ false).edata == NULL); | ||
| 173 | emap_rtree_write_acquired(tsdn, emap, elm_a, elm_b, edata, szind, slab); | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | |||
| 177 | /* Invoked *after* emap_register_boundary. */ | ||
| 178 | void | ||
| 179 | emap_register_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata, | ||
| 180 | szind_t szind) { | ||
| 181 | EMAP_DECLARE_RTREE_CTX; | ||
| 182 | |||
| 183 | assert(edata_slab_get(edata)); | ||
| 184 | assert(edata_state_get(edata) == extent_state_active); | ||
| 185 | |||
| 186 | if (config_debug) { | ||
| 187 | /* Making sure the boundary is registered already. */ | ||
| 188 | rtree_leaf_elm_t *elm_a, *elm_b; | ||
| 189 | bool err = emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, | ||
| 190 | edata, /* dependent */ true, /* init_missing */ false, | ||
| 191 | &elm_a, &elm_b); | ||
| 192 | assert(!err); | ||
| 193 | rtree_contents_t contents_a, contents_b; | ||
| 194 | contents_a = rtree_leaf_elm_read(tsdn, &emap->rtree, elm_a, | ||
| 195 | /* dependent */ true); | ||
| 196 | contents_b = rtree_leaf_elm_read(tsdn, &emap->rtree, elm_b, | ||
| 197 | /* dependent */ true); | ||
| 198 | assert(contents_a.edata == edata && contents_b.edata == edata); | ||
| 199 | assert(contents_a.metadata.slab && contents_b.metadata.slab); | ||
| 200 | } | ||
| 201 | |||
| 202 | rtree_contents_t contents; | ||
| 203 | contents.edata = edata; | ||
| 204 | contents.metadata.szind = szind; | ||
| 205 | contents.metadata.slab = true; | ||
| 206 | contents.metadata.state = extent_state_active; | ||
| 207 | contents.metadata.is_head = false; /* Not allowed to access. */ | ||
| 208 | |||
| 209 | assert(edata_size_get(edata) > (2 << LG_PAGE)); | ||
| 210 | rtree_write_range(tsdn, &emap->rtree, rtree_ctx, | ||
| 211 | (uintptr_t)edata_base_get(edata) + PAGE, | ||
| 212 | (uintptr_t)edata_last_get(edata) - PAGE, contents); | ||
| 213 | } | ||
| 214 | |||
| 215 | void | ||
| 216 | emap_deregister_boundary(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { | ||
| 217 | /* | ||
| 218 | * The edata must be either in an acquired state, or protected by state | ||
| 219 | * based locks. | ||
| 220 | */ | ||
| 221 | if (!emap_edata_is_acquired(tsdn, emap, edata)) { | ||
| 222 | witness_assert_positive_depth_to_rank( | ||
| 223 | tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); | ||
| 224 | } | ||
| 225 | |||
| 226 | EMAP_DECLARE_RTREE_CTX; | ||
| 227 | rtree_leaf_elm_t *elm_a, *elm_b; | ||
| 228 | |||
| 229 | emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, edata, | ||
| 230 | true, false, &elm_a, &elm_b); | ||
| 231 | emap_rtree_write_acquired(tsdn, emap, elm_a, elm_b, NULL, SC_NSIZES, | ||
| 232 | false); | ||
| 233 | } | ||
| 234 | |||
| 235 | void | ||
| 236 | emap_deregister_interior(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { | ||
| 237 | EMAP_DECLARE_RTREE_CTX; | ||
| 238 | |||
| 239 | assert(edata_slab_get(edata)); | ||
| 240 | if (edata_size_get(edata) > (2 << LG_PAGE)) { | ||
| 241 | rtree_clear_range(tsdn, &emap->rtree, rtree_ctx, | ||
| 242 | (uintptr_t)edata_base_get(edata) + PAGE, | ||
| 243 | (uintptr_t)edata_last_get(edata) - PAGE); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | void | ||
| 248 | emap_remap(tsdn_t *tsdn, emap_t *emap, edata_t *edata, szind_t szind, | ||
| 249 | bool slab) { | ||
| 250 | EMAP_DECLARE_RTREE_CTX; | ||
| 251 | |||
| 252 | if (szind != SC_NSIZES) { | ||
| 253 | rtree_contents_t contents; | ||
| 254 | contents.edata = edata; | ||
| 255 | contents.metadata.szind = szind; | ||
| 256 | contents.metadata.slab = slab; | ||
| 257 | contents.metadata.is_head = edata_is_head_get(edata); | ||
| 258 | contents.metadata.state = edata_state_get(edata); | ||
| 259 | |||
| 260 | rtree_write(tsdn, &emap->rtree, rtree_ctx, | ||
| 261 | (uintptr_t)edata_addr_get(edata), contents); | ||
| 262 | /* | ||
| 263 | * Recall that this is called only for active->inactive and | ||
| 264 | * inactive->active transitions (since only active extents have | ||
| 265 | * meaningful values for szind and slab). Active, non-slab | ||
| 266 | * extents only need to handle lookups at their head (on | ||
| 267 | * deallocation), so we don't bother filling in the end | ||
| 268 | * boundary. | ||
| 269 | * | ||
| 270 | * For slab extents, we do the end-mapping change. This still | ||
| 271 | * leaves the interior unmodified; an emap_register_interior | ||
| 272 | * call is coming in those cases, though. | ||
| 273 | */ | ||
| 274 | if (slab && edata_size_get(edata) > PAGE) { | ||
| 275 | uintptr_t key = (uintptr_t)edata_past_get(edata) | ||
| 276 | - (uintptr_t)PAGE; | ||
| 277 | rtree_write(tsdn, &emap->rtree, rtree_ctx, key, | ||
| 278 | contents); | ||
| 279 | } | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | bool | ||
| 284 | emap_split_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, | ||
| 285 | edata_t *edata, size_t size_a, edata_t *trail, size_t size_b) { | ||
| 286 | EMAP_DECLARE_RTREE_CTX; | ||
| 287 | |||
| 288 | /* | ||
| 289 | * We use incorrect constants for things like arena ind, zero, ranged, | ||
| 290 | * and commit state, and head status. This is a fake edata_t, used to | ||
| 291 | * facilitate a lookup. | ||
| 292 | */ | ||
| 293 | edata_t lead = {0}; | ||
| 294 | edata_init(&lead, 0U, edata_addr_get(edata), size_a, false, 0, 0, | ||
| 295 | extent_state_active, false, false, EXTENT_PAI_PAC, EXTENT_NOT_HEAD); | ||
| 296 | |||
| 297 | emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, &lead, false, true, | ||
| 298 | &prepare->lead_elm_a, &prepare->lead_elm_b); | ||
| 299 | emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, trail, false, true, | ||
| 300 | &prepare->trail_elm_a, &prepare->trail_elm_b); | ||
| 301 | |||
| 302 | if (prepare->lead_elm_a == NULL || prepare->lead_elm_b == NULL | ||
| 303 | || prepare->trail_elm_a == NULL || prepare->trail_elm_b == NULL) { | ||
| 304 | return true; | ||
| 305 | } | ||
| 306 | return false; | ||
| 307 | } | ||
| 308 | |||
| 309 | void | ||
| 310 | emap_split_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, | ||
| 311 | edata_t *lead, size_t size_a, edata_t *trail, size_t size_b) { | ||
| 312 | /* | ||
| 313 | * We should think about not writing to the lead leaf element. We can | ||
| 314 | * get into situations where a racing realloc-like call can disagree | ||
| 315 | * with a size lookup request. I think it's fine to declare that these | ||
| 316 | * situations are race bugs, but there's an argument to be made that for | ||
| 317 | * things like xallocx, a size lookup call should return either the old | ||
| 318 | * size or the new size, but not anything else. | ||
| 319 | */ | ||
| 320 | emap_rtree_write_acquired(tsdn, emap, prepare->lead_elm_a, | ||
| 321 | prepare->lead_elm_b, lead, SC_NSIZES, /* slab */ false); | ||
| 322 | emap_rtree_write_acquired(tsdn, emap, prepare->trail_elm_a, | ||
| 323 | prepare->trail_elm_b, trail, SC_NSIZES, /* slab */ false); | ||
| 324 | } | ||
| 325 | |||
| 326 | void | ||
| 327 | emap_merge_prepare(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, | ||
| 328 | edata_t *lead, edata_t *trail) { | ||
| 329 | EMAP_DECLARE_RTREE_CTX; | ||
| 330 | emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, lead, true, false, | ||
| 331 | &prepare->lead_elm_a, &prepare->lead_elm_b); | ||
| 332 | emap_rtree_leaf_elms_lookup(tsdn, emap, rtree_ctx, trail, true, false, | ||
| 333 | &prepare->trail_elm_a, &prepare->trail_elm_b); | ||
| 334 | } | ||
| 335 | |||
| 336 | void | ||
| 337 | emap_merge_commit(tsdn_t *tsdn, emap_t *emap, emap_prepare_t *prepare, | ||
| 338 | edata_t *lead, edata_t *trail) { | ||
| 339 | rtree_contents_t clear_contents; | ||
| 340 | clear_contents.edata = NULL; | ||
| 341 | clear_contents.metadata.szind = SC_NSIZES; | ||
| 342 | clear_contents.metadata.slab = false; | ||
| 343 | clear_contents.metadata.is_head = false; | ||
| 344 | clear_contents.metadata.state = (extent_state_t)0; | ||
| 345 | |||
| 346 | if (prepare->lead_elm_b != NULL) { | ||
| 347 | rtree_leaf_elm_write(tsdn, &emap->rtree, | ||
| 348 | prepare->lead_elm_b, clear_contents); | ||
| 349 | } | ||
| 350 | |||
| 351 | rtree_leaf_elm_t *merged_b; | ||
| 352 | if (prepare->trail_elm_b != NULL) { | ||
| 353 | rtree_leaf_elm_write(tsdn, &emap->rtree, | ||
| 354 | prepare->trail_elm_a, clear_contents); | ||
| 355 | merged_b = prepare->trail_elm_b; | ||
| 356 | } else { | ||
| 357 | merged_b = prepare->trail_elm_a; | ||
| 358 | } | ||
| 359 | |||
| 360 | emap_rtree_write_acquired(tsdn, emap, prepare->lead_elm_a, merged_b, | ||
| 361 | lead, SC_NSIZES, false); | ||
| 362 | } | ||
| 363 | |||
| 364 | void | ||
| 365 | emap_do_assert_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { | ||
| 366 | EMAP_DECLARE_RTREE_CTX; | ||
| 367 | |||
| 368 | rtree_contents_t contents = rtree_read(tsdn, &emap->rtree, rtree_ctx, | ||
| 369 | (uintptr_t)edata_base_get(edata)); | ||
| 370 | assert(contents.edata == edata); | ||
| 371 | assert(contents.metadata.is_head == edata_is_head_get(edata)); | ||
| 372 | assert(contents.metadata.state == edata_state_get(edata)); | ||
| 373 | } | ||
| 374 | |||
| 375 | void | ||
| 376 | emap_do_assert_not_mapped(tsdn_t *tsdn, emap_t *emap, edata_t *edata) { | ||
| 377 | emap_full_alloc_ctx_t context1 = {0}; | ||
| 378 | emap_full_alloc_ctx_try_lookup(tsdn, emap, edata_base_get(edata), | ||
| 379 | &context1); | ||
| 380 | assert(context1.edata == NULL); | ||
| 381 | |||
| 382 | emap_full_alloc_ctx_t context2 = {0}; | ||
| 383 | emap_full_alloc_ctx_try_lookup(tsdn, emap, edata_last_get(edata), | ||
| 384 | &context2); | ||
| 385 | assert(context2.edata == NULL); | ||
| 386 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/eset.c b/examples/redis-unstable/deps/jemalloc/src/eset.c deleted file mode 100644 index 6f8f335..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/eset.c +++ /dev/null | |||
| @@ -1,282 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/eset.h" | ||
| 5 | |||
| 6 | #define ESET_NPSIZES (SC_NPSIZES + 1) | ||
| 7 | |||
| 8 | static void | ||
| 9 | eset_bin_init(eset_bin_t *bin) { | ||
| 10 | edata_heap_new(&bin->heap); | ||
| 11 | /* | ||
| 12 | * heap_min doesn't need initialization; it gets filled in when the bin | ||
| 13 | * goes from non-empty to empty. | ||
| 14 | */ | ||
| 15 | } | ||
| 16 | |||
| 17 | static void | ||
| 18 | eset_bin_stats_init(eset_bin_stats_t *bin_stats) { | ||
| 19 | atomic_store_zu(&bin_stats->nextents, 0, ATOMIC_RELAXED); | ||
| 20 | atomic_store_zu(&bin_stats->nbytes, 0, ATOMIC_RELAXED); | ||
| 21 | } | ||
| 22 | |||
| 23 | void | ||
| 24 | eset_init(eset_t *eset, extent_state_t state) { | ||
| 25 | for (unsigned i = 0; i < ESET_NPSIZES; i++) { | ||
| 26 | eset_bin_init(&eset->bins[i]); | ||
| 27 | eset_bin_stats_init(&eset->bin_stats[i]); | ||
| 28 | } | ||
| 29 | fb_init(eset->bitmap, ESET_NPSIZES); | ||
| 30 | edata_list_inactive_init(&eset->lru); | ||
| 31 | eset->state = state; | ||
| 32 | } | ||
| 33 | |||
| 34 | size_t | ||
| 35 | eset_npages_get(eset_t *eset) { | ||
| 36 | return atomic_load_zu(&eset->npages, ATOMIC_RELAXED); | ||
| 37 | } | ||
| 38 | |||
| 39 | size_t | ||
| 40 | eset_nextents_get(eset_t *eset, pszind_t pind) { | ||
| 41 | return atomic_load_zu(&eset->bin_stats[pind].nextents, ATOMIC_RELAXED); | ||
| 42 | } | ||
| 43 | |||
| 44 | size_t | ||
| 45 | eset_nbytes_get(eset_t *eset, pszind_t pind) { | ||
| 46 | return atomic_load_zu(&eset->bin_stats[pind].nbytes, ATOMIC_RELAXED); | ||
| 47 | } | ||
| 48 | |||
| 49 | static void | ||
| 50 | eset_stats_add(eset_t *eset, pszind_t pind, size_t sz) { | ||
| 51 | size_t cur = atomic_load_zu(&eset->bin_stats[pind].nextents, | ||
| 52 | ATOMIC_RELAXED); | ||
| 53 | atomic_store_zu(&eset->bin_stats[pind].nextents, cur + 1, | ||
| 54 | ATOMIC_RELAXED); | ||
| 55 | cur = atomic_load_zu(&eset->bin_stats[pind].nbytes, ATOMIC_RELAXED); | ||
| 56 | atomic_store_zu(&eset->bin_stats[pind].nbytes, cur + sz, | ||
| 57 | ATOMIC_RELAXED); | ||
| 58 | } | ||
| 59 | |||
| 60 | static void | ||
| 61 | eset_stats_sub(eset_t *eset, pszind_t pind, size_t sz) { | ||
| 62 | size_t cur = atomic_load_zu(&eset->bin_stats[pind].nextents, | ||
| 63 | ATOMIC_RELAXED); | ||
| 64 | atomic_store_zu(&eset->bin_stats[pind].nextents, cur - 1, | ||
| 65 | ATOMIC_RELAXED); | ||
| 66 | cur = atomic_load_zu(&eset->bin_stats[pind].nbytes, ATOMIC_RELAXED); | ||
| 67 | atomic_store_zu(&eset->bin_stats[pind].nbytes, cur - sz, | ||
| 68 | ATOMIC_RELAXED); | ||
| 69 | } | ||
| 70 | |||
| 71 | void | ||
| 72 | eset_insert(eset_t *eset, edata_t *edata) { | ||
| 73 | assert(edata_state_get(edata) == eset->state); | ||
| 74 | |||
| 75 | size_t size = edata_size_get(edata); | ||
| 76 | size_t psz = sz_psz_quantize_floor(size); | ||
| 77 | pszind_t pind = sz_psz2ind(psz); | ||
| 78 | |||
| 79 | edata_cmp_summary_t edata_cmp_summary = edata_cmp_summary_get(edata); | ||
| 80 | if (edata_heap_empty(&eset->bins[pind].heap)) { | ||
| 81 | fb_set(eset->bitmap, ESET_NPSIZES, (size_t)pind); | ||
| 82 | /* Only element is automatically the min element. */ | ||
| 83 | eset->bins[pind].heap_min = edata_cmp_summary; | ||
| 84 | } else { | ||
| 85 | /* | ||
| 86 | * There's already a min element; update the summary if we're | ||
| 87 | * about to insert a lower one. | ||
| 88 | */ | ||
| 89 | if (edata_cmp_summary_comp(edata_cmp_summary, | ||
| 90 | eset->bins[pind].heap_min) < 0) { | ||
| 91 | eset->bins[pind].heap_min = edata_cmp_summary; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | edata_heap_insert(&eset->bins[pind].heap, edata); | ||
| 95 | |||
| 96 | if (config_stats) { | ||
| 97 | eset_stats_add(eset, pind, size); | ||
| 98 | } | ||
| 99 | |||
| 100 | edata_list_inactive_append(&eset->lru, edata); | ||
| 101 | size_t npages = size >> LG_PAGE; | ||
| 102 | /* | ||
| 103 | * All modifications to npages hold the mutex (as asserted above), so we | ||
| 104 | * don't need an atomic fetch-add; we can get by with a load followed by | ||
| 105 | * a store. | ||
| 106 | */ | ||
| 107 | size_t cur_eset_npages = | ||
| 108 | atomic_load_zu(&eset->npages, ATOMIC_RELAXED); | ||
| 109 | atomic_store_zu(&eset->npages, cur_eset_npages + npages, | ||
| 110 | ATOMIC_RELAXED); | ||
| 111 | } | ||
| 112 | |||
| 113 | void | ||
| 114 | eset_remove(eset_t *eset, edata_t *edata) { | ||
| 115 | assert(edata_state_get(edata) == eset->state || | ||
| 116 | edata_state_in_transition(edata_state_get(edata))); | ||
| 117 | |||
| 118 | size_t size = edata_size_get(edata); | ||
| 119 | size_t psz = sz_psz_quantize_floor(size); | ||
| 120 | pszind_t pind = sz_psz2ind(psz); | ||
| 121 | if (config_stats) { | ||
| 122 | eset_stats_sub(eset, pind, size); | ||
| 123 | } | ||
| 124 | |||
| 125 | edata_cmp_summary_t edata_cmp_summary = edata_cmp_summary_get(edata); | ||
| 126 | edata_heap_remove(&eset->bins[pind].heap, edata); | ||
| 127 | if (edata_heap_empty(&eset->bins[pind].heap)) { | ||
| 128 | fb_unset(eset->bitmap, ESET_NPSIZES, (size_t)pind); | ||
| 129 | } else { | ||
| 130 | /* | ||
| 131 | * This is a little weird; we compare if the summaries are | ||
| 132 | * equal, rather than if the edata we removed was the heap | ||
| 133 | * minimum. The reason why is that getting the heap minimum | ||
| 134 | * can cause a pairing heap merge operation. We can avoid this | ||
| 135 | * if we only update the min if it's changed, in which case the | ||
| 136 | * summaries of the removed element and the min element should | ||
| 137 | * compare equal. | ||
| 138 | */ | ||
| 139 | if (edata_cmp_summary_comp(edata_cmp_summary, | ||
| 140 | eset->bins[pind].heap_min) == 0) { | ||
| 141 | eset->bins[pind].heap_min = edata_cmp_summary_get( | ||
| 142 | edata_heap_first(&eset->bins[pind].heap)); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | edata_list_inactive_remove(&eset->lru, edata); | ||
| 146 | size_t npages = size >> LG_PAGE; | ||
| 147 | /* | ||
| 148 | * As in eset_insert, we hold eset->mtx and so don't need atomic | ||
| 149 | * operations for updating eset->npages. | ||
| 150 | */ | ||
| 151 | size_t cur_extents_npages = | ||
| 152 | atomic_load_zu(&eset->npages, ATOMIC_RELAXED); | ||
| 153 | assert(cur_extents_npages >= npages); | ||
| 154 | atomic_store_zu(&eset->npages, | ||
| 155 | cur_extents_npages - (size >> LG_PAGE), ATOMIC_RELAXED); | ||
| 156 | } | ||
| 157 | |||
| 158 | /* | ||
| 159 | * Find an extent with size [min_size, max_size) to satisfy the alignment | ||
| 160 | * requirement. For each size, try only the first extent in the heap. | ||
| 161 | */ | ||
| 162 | static edata_t * | ||
| 163 | eset_fit_alignment(eset_t *eset, size_t min_size, size_t max_size, | ||
| 164 | size_t alignment) { | ||
| 165 | pszind_t pind = sz_psz2ind(sz_psz_quantize_ceil(min_size)); | ||
| 166 | pszind_t pind_max = sz_psz2ind(sz_psz_quantize_ceil(max_size)); | ||
| 167 | |||
| 168 | for (pszind_t i = | ||
| 169 | (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)pind); | ||
| 170 | i < pind_max; | ||
| 171 | i = (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)i + 1)) { | ||
| 172 | assert(i < SC_NPSIZES); | ||
| 173 | assert(!edata_heap_empty(&eset->bins[i].heap)); | ||
| 174 | edata_t *edata = edata_heap_first(&eset->bins[i].heap); | ||
| 175 | uintptr_t base = (uintptr_t)edata_base_get(edata); | ||
| 176 | size_t candidate_size = edata_size_get(edata); | ||
| 177 | assert(candidate_size >= min_size); | ||
| 178 | |||
| 179 | uintptr_t next_align = ALIGNMENT_CEILING((uintptr_t)base, | ||
| 180 | PAGE_CEILING(alignment)); | ||
| 181 | if (base > next_align || base + candidate_size <= next_align) { | ||
| 182 | /* Overflow or not crossing the next alignment. */ | ||
| 183 | continue; | ||
| 184 | } | ||
| 185 | |||
| 186 | size_t leadsize = next_align - base; | ||
| 187 | if (candidate_size - leadsize >= min_size) { | ||
| 188 | return edata; | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | return NULL; | ||
| 193 | } | ||
| 194 | |||
| 195 | /* | ||
| 196 | * Do first-fit extent selection, i.e. select the oldest/lowest extent that is | ||
| 197 | * large enough. | ||
| 198 | * | ||
| 199 | * lg_max_fit is the (log of the) maximum ratio between the requested size and | ||
| 200 | * the returned size that we'll allow. This can reduce fragmentation by | ||
| 201 | * avoiding reusing and splitting large extents for smaller sizes. In practice, | ||
| 202 | * it's set to opt_lg_extent_max_active_fit for the dirty eset and SC_PTR_BITS | ||
| 203 | * for others. | ||
| 204 | */ | ||
| 205 | static edata_t * | ||
| 206 | eset_first_fit(eset_t *eset, size_t size, bool exact_only, | ||
| 207 | unsigned lg_max_fit) { | ||
| 208 | edata_t *ret = NULL; | ||
| 209 | edata_cmp_summary_t ret_summ JEMALLOC_CC_SILENCE_INIT({0}); | ||
| 210 | |||
| 211 | pszind_t pind = sz_psz2ind(sz_psz_quantize_ceil(size)); | ||
| 212 | |||
| 213 | if (exact_only) { | ||
| 214 | return edata_heap_empty(&eset->bins[pind].heap) ? NULL : | ||
| 215 | edata_heap_first(&eset->bins[pind].heap); | ||
| 216 | } | ||
| 217 | |||
| 218 | for (pszind_t i = | ||
| 219 | (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)pind); | ||
| 220 | i < ESET_NPSIZES; | ||
| 221 | i = (pszind_t)fb_ffs(eset->bitmap, ESET_NPSIZES, (size_t)i + 1)) { | ||
| 222 | assert(!edata_heap_empty(&eset->bins[i].heap)); | ||
| 223 | if (lg_max_fit == SC_PTR_BITS) { | ||
| 224 | /* | ||
| 225 | * We'll shift by this below, and shifting out all the | ||
| 226 | * bits is undefined. Decreasing is safe, since the | ||
| 227 | * page size is larger than 1 byte. | ||
| 228 | */ | ||
| 229 | lg_max_fit = SC_PTR_BITS - 1; | ||
| 230 | } | ||
| 231 | if ((sz_pind2sz(i) >> lg_max_fit) > size) { | ||
| 232 | break; | ||
| 233 | } | ||
| 234 | if (ret == NULL || edata_cmp_summary_comp( | ||
| 235 | eset->bins[i].heap_min, ret_summ) < 0) { | ||
| 236 | /* | ||
| 237 | * We grab the edata as early as possible, even though | ||
| 238 | * we might change it later. Practically, a large | ||
| 239 | * portion of eset_fit calls succeed at the first valid | ||
| 240 | * index, so this doesn't cost much, and we get the | ||
| 241 | * effect of prefetching the edata as early as possible. | ||
| 242 | */ | ||
| 243 | edata_t *edata = edata_heap_first(&eset->bins[i].heap); | ||
| 244 | assert(edata_size_get(edata) >= size); | ||
| 245 | assert(ret == NULL || edata_snad_comp(edata, ret) < 0); | ||
| 246 | assert(ret == NULL || edata_cmp_summary_comp( | ||
| 247 | eset->bins[i].heap_min, | ||
| 248 | edata_cmp_summary_get(edata)) == 0); | ||
| 249 | ret = edata; | ||
| 250 | ret_summ = eset->bins[i].heap_min; | ||
| 251 | } | ||
| 252 | if (i == SC_NPSIZES) { | ||
| 253 | break; | ||
| 254 | } | ||
| 255 | assert(i < SC_NPSIZES); | ||
| 256 | } | ||
| 257 | |||
| 258 | return ret; | ||
| 259 | } | ||
| 260 | |||
| 261 | edata_t * | ||
| 262 | eset_fit(eset_t *eset, size_t esize, size_t alignment, bool exact_only, | ||
| 263 | unsigned lg_max_fit) { | ||
| 264 | size_t max_size = esize + PAGE_CEILING(alignment) - PAGE; | ||
| 265 | /* Beware size_t wrap-around. */ | ||
| 266 | if (max_size < esize) { | ||
| 267 | return NULL; | ||
| 268 | } | ||
| 269 | |||
| 270 | edata_t *edata = eset_first_fit(eset, max_size, exact_only, lg_max_fit); | ||
| 271 | |||
| 272 | if (alignment > PAGE && edata == NULL) { | ||
| 273 | /* | ||
| 274 | * max_size guarantees the alignment requirement but is rather | ||
| 275 | * pessimistic. Next we try to satisfy the aligned allocation | ||
| 276 | * with sizes in [esize, max_size). | ||
| 277 | */ | ||
| 278 | edata = eset_fit_alignment(eset, esize, max_size, alignment); | ||
| 279 | } | ||
| 280 | |||
| 281 | return edata; | ||
| 282 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/exp_grow.c b/examples/redis-unstable/deps/jemalloc/src/exp_grow.c deleted file mode 100644 index 386471f..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/exp_grow.c +++ /dev/null | |||
| @@ -1,8 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | void | ||
| 5 | exp_grow_init(exp_grow_t *exp_grow) { | ||
| 6 | exp_grow->next = sz_psz2ind(HUGEPAGE); | ||
| 7 | exp_grow->limit = sz_psz2ind(SC_LARGE_MAXCLASS); | ||
| 8 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/extent.c b/examples/redis-unstable/deps/jemalloc/src/extent.c deleted file mode 100644 index cf3d1f3..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/extent.c +++ /dev/null | |||
| @@ -1,1326 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/emap.h" | ||
| 6 | #include "jemalloc/internal/extent_dss.h" | ||
| 7 | #include "jemalloc/internal/extent_mmap.h" | ||
| 8 | #include "jemalloc/internal/ph.h" | ||
| 9 | #include "jemalloc/internal/mutex.h" | ||
| 10 | |||
| 11 | /******************************************************************************/ | ||
| 12 | /* Data. */ | ||
| 13 | |||
| 14 | size_t opt_lg_extent_max_active_fit = LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT; | ||
| 15 | |||
| 16 | static bool extent_commit_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 17 | size_t offset, size_t length, bool growing_retained); | ||
| 18 | static bool extent_purge_lazy_impl(tsdn_t *tsdn, ehooks_t *ehooks, | ||
| 19 | edata_t *edata, size_t offset, size_t length, bool growing_retained); | ||
| 20 | static bool extent_purge_forced_impl(tsdn_t *tsdn, ehooks_t *ehooks, | ||
| 21 | edata_t *edata, size_t offset, size_t length, bool growing_retained); | ||
| 22 | static edata_t *extent_split_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 23 | edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks); | ||
| 24 | static bool extent_merge_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 25 | edata_t *a, edata_t *b, bool holding_core_locks); | ||
| 26 | |||
| 27 | /* Used exclusively for gdump triggering. */ | ||
| 28 | static atomic_zu_t curpages; | ||
| 29 | static atomic_zu_t highpages; | ||
| 30 | |||
| 31 | /******************************************************************************/ | ||
| 32 | /* | ||
| 33 | * Function prototypes for static functions that are referenced prior to | ||
| 34 | * definition. | ||
| 35 | */ | ||
| 36 | |||
| 37 | static void extent_deregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata); | ||
| 38 | static edata_t *extent_recycle(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 39 | ecache_t *ecache, edata_t *expand_edata, size_t usize, size_t alignment, | ||
| 40 | bool zero, bool *commit, bool growing_retained, bool guarded); | ||
| 41 | static edata_t *extent_try_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 42 | ecache_t *ecache, edata_t *edata, bool *coalesced); | ||
| 43 | static edata_t *extent_alloc_retained(tsdn_t *tsdn, pac_t *pac, | ||
| 44 | ehooks_t *ehooks, edata_t *expand_edata, size_t size, size_t alignment, | ||
| 45 | bool zero, bool *commit, bool guarded); | ||
| 46 | |||
| 47 | /******************************************************************************/ | ||
| 48 | |||
| 49 | size_t | ||
| 50 | extent_sn_next(pac_t *pac) { | ||
| 51 | return atomic_fetch_add_zu(&pac->extent_sn_next, 1, ATOMIC_RELAXED); | ||
| 52 | } | ||
| 53 | |||
| 54 | static inline bool | ||
| 55 | extent_may_force_decay(pac_t *pac) { | ||
| 56 | return !(pac_decay_ms_get(pac, extent_state_dirty) == -1 | ||
| 57 | || pac_decay_ms_get(pac, extent_state_muzzy) == -1); | ||
| 58 | } | ||
| 59 | |||
| 60 | static bool | ||
| 61 | extent_try_delayed_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 62 | ecache_t *ecache, edata_t *edata) { | ||
| 63 | emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active); | ||
| 64 | |||
| 65 | bool coalesced; | ||
| 66 | edata = extent_try_coalesce(tsdn, pac, ehooks, ecache, | ||
| 67 | edata, &coalesced); | ||
| 68 | emap_update_edata_state(tsdn, pac->emap, edata, ecache->state); | ||
| 69 | |||
| 70 | if (!coalesced) { | ||
| 71 | return true; | ||
| 72 | } | ||
| 73 | eset_insert(&ecache->eset, edata); | ||
| 74 | return false; | ||
| 75 | } | ||
| 76 | |||
| 77 | edata_t * | ||
| 78 | ecache_alloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 79 | edata_t *expand_edata, size_t size, size_t alignment, bool zero, | ||
| 80 | bool guarded) { | ||
| 81 | assert(size != 0); | ||
| 82 | assert(alignment != 0); | ||
| 83 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 84 | WITNESS_RANK_CORE, 0); | ||
| 85 | |||
| 86 | bool commit = true; | ||
| 87 | edata_t *edata = extent_recycle(tsdn, pac, ehooks, ecache, expand_edata, | ||
| 88 | size, alignment, zero, &commit, false, guarded); | ||
| 89 | assert(edata == NULL || edata_pai_get(edata) == EXTENT_PAI_PAC); | ||
| 90 | assert(edata == NULL || edata_guarded_get(edata) == guarded); | ||
| 91 | return edata; | ||
| 92 | } | ||
| 93 | |||
| 94 | edata_t * | ||
| 95 | ecache_alloc_grow(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 96 | edata_t *expand_edata, size_t size, size_t alignment, bool zero, | ||
| 97 | bool guarded) { | ||
| 98 | assert(size != 0); | ||
| 99 | assert(alignment != 0); | ||
| 100 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 101 | WITNESS_RANK_CORE, 0); | ||
| 102 | |||
| 103 | bool commit = true; | ||
| 104 | edata_t *edata = extent_alloc_retained(tsdn, pac, ehooks, expand_edata, | ||
| 105 | size, alignment, zero, &commit, guarded); | ||
| 106 | if (edata == NULL) { | ||
| 107 | if (opt_retain && expand_edata != NULL) { | ||
| 108 | /* | ||
| 109 | * When retain is enabled and trying to expand, we do | ||
| 110 | * not attempt extent_alloc_wrapper which does mmap that | ||
| 111 | * is very unlikely to succeed (unless it happens to be | ||
| 112 | * at the end). | ||
| 113 | */ | ||
| 114 | return NULL; | ||
| 115 | } | ||
| 116 | if (guarded) { | ||
| 117 | /* | ||
| 118 | * Means no cached guarded extents available (and no | ||
| 119 | * grow_retained was attempted). The pac_alloc flow | ||
| 120 | * will alloc regular extents to make new guarded ones. | ||
| 121 | */ | ||
| 122 | return NULL; | ||
| 123 | } | ||
| 124 | void *new_addr = (expand_edata == NULL) ? NULL : | ||
| 125 | edata_past_get(expand_edata); | ||
| 126 | edata = extent_alloc_wrapper(tsdn, pac, ehooks, new_addr, | ||
| 127 | size, alignment, zero, &commit, | ||
| 128 | /* growing_retained */ false); | ||
| 129 | } | ||
| 130 | |||
| 131 | assert(edata == NULL || edata_pai_get(edata) == EXTENT_PAI_PAC); | ||
| 132 | return edata; | ||
| 133 | } | ||
| 134 | |||
| 135 | void | ||
| 136 | ecache_dalloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 137 | edata_t *edata) { | ||
| 138 | assert(edata_base_get(edata) != NULL); | ||
| 139 | assert(edata_size_get(edata) != 0); | ||
| 140 | assert(edata_pai_get(edata) == EXTENT_PAI_PAC); | ||
| 141 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 142 | WITNESS_RANK_CORE, 0); | ||
| 143 | |||
| 144 | edata_addr_set(edata, edata_base_get(edata)); | ||
| 145 | edata_zeroed_set(edata, false); | ||
| 146 | |||
| 147 | extent_record(tsdn, pac, ehooks, ecache, edata); | ||
| 148 | } | ||
| 149 | |||
| 150 | edata_t * | ||
| 151 | ecache_evict(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 152 | ecache_t *ecache, size_t npages_min) { | ||
| 153 | malloc_mutex_lock(tsdn, &ecache->mtx); | ||
| 154 | |||
| 155 | /* | ||
| 156 | * Get the LRU coalesced extent, if any. If coalescing was delayed, | ||
| 157 | * the loop will iterate until the LRU extent is fully coalesced. | ||
| 158 | */ | ||
| 159 | edata_t *edata; | ||
| 160 | while (true) { | ||
| 161 | /* Get the LRU extent, if any. */ | ||
| 162 | eset_t *eset = &ecache->eset; | ||
| 163 | edata = edata_list_inactive_first(&eset->lru); | ||
| 164 | if (edata == NULL) { | ||
| 165 | /* | ||
| 166 | * Next check if there are guarded extents. They are | ||
| 167 | * more expensive to purge (since they are not | ||
| 168 | * mergeable), thus in favor of caching them longer. | ||
| 169 | */ | ||
| 170 | eset = &ecache->guarded_eset; | ||
| 171 | edata = edata_list_inactive_first(&eset->lru); | ||
| 172 | if (edata == NULL) { | ||
| 173 | goto label_return; | ||
| 174 | } | ||
| 175 | } | ||
| 176 | /* Check the eviction limit. */ | ||
| 177 | size_t extents_npages = ecache_npages_get(ecache); | ||
| 178 | if (extents_npages <= npages_min) { | ||
| 179 | edata = NULL; | ||
| 180 | goto label_return; | ||
| 181 | } | ||
| 182 | eset_remove(eset, edata); | ||
| 183 | if (!ecache->delay_coalesce || edata_guarded_get(edata)) { | ||
| 184 | break; | ||
| 185 | } | ||
| 186 | /* Try to coalesce. */ | ||
| 187 | if (extent_try_delayed_coalesce(tsdn, pac, ehooks, ecache, | ||
| 188 | edata)) { | ||
| 189 | break; | ||
| 190 | } | ||
| 191 | /* | ||
| 192 | * The LRU extent was just coalesced and the result placed in | ||
| 193 | * the LRU at its neighbor's position. Start over. | ||
| 194 | */ | ||
| 195 | } | ||
| 196 | |||
| 197 | /* | ||
| 198 | * Either mark the extent active or deregister it to protect against | ||
| 199 | * concurrent operations. | ||
| 200 | */ | ||
| 201 | switch (ecache->state) { | ||
| 202 | case extent_state_active: | ||
| 203 | not_reached(); | ||
| 204 | case extent_state_dirty: | ||
| 205 | case extent_state_muzzy: | ||
| 206 | emap_update_edata_state(tsdn, pac->emap, edata, | ||
| 207 | extent_state_active); | ||
| 208 | break; | ||
| 209 | case extent_state_retained: | ||
| 210 | extent_deregister(tsdn, pac, edata); | ||
| 211 | break; | ||
| 212 | default: | ||
| 213 | not_reached(); | ||
| 214 | } | ||
| 215 | |||
| 216 | label_return: | ||
| 217 | malloc_mutex_unlock(tsdn, &ecache->mtx); | ||
| 218 | return edata; | ||
| 219 | } | ||
| 220 | |||
| 221 | /* | ||
| 222 | * This can only happen when we fail to allocate a new extent struct (which | ||
| 223 | * indicates OOM), e.g. when trying to split an existing extent. | ||
| 224 | */ | ||
| 225 | static void | ||
| 226 | extents_abandon_vm(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 227 | edata_t *edata, bool growing_retained) { | ||
| 228 | size_t sz = edata_size_get(edata); | ||
| 229 | if (config_stats) { | ||
| 230 | atomic_fetch_add_zu(&pac->stats->abandoned_vm, sz, | ||
| 231 | ATOMIC_RELAXED); | ||
| 232 | } | ||
| 233 | /* | ||
| 234 | * Leak extent after making sure its pages have already been purged, so | ||
| 235 | * that this is only a virtual memory leak. | ||
| 236 | */ | ||
| 237 | if (ecache->state == extent_state_dirty) { | ||
| 238 | if (extent_purge_lazy_impl(tsdn, ehooks, edata, 0, sz, | ||
| 239 | growing_retained)) { | ||
| 240 | extent_purge_forced_impl(tsdn, ehooks, edata, 0, | ||
| 241 | edata_size_get(edata), growing_retained); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 245 | } | ||
| 246 | |||
| 247 | static void | ||
| 248 | extent_deactivate_locked_impl(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, | ||
| 249 | edata_t *edata) { | ||
| 250 | malloc_mutex_assert_owner(tsdn, &ecache->mtx); | ||
| 251 | assert(edata_arena_ind_get(edata) == ecache_ind_get(ecache)); | ||
| 252 | |||
| 253 | emap_update_edata_state(tsdn, pac->emap, edata, ecache->state); | ||
| 254 | eset_t *eset = edata_guarded_get(edata) ? &ecache->guarded_eset : | ||
| 255 | &ecache->eset; | ||
| 256 | eset_insert(eset, edata); | ||
| 257 | } | ||
| 258 | |||
| 259 | static void | ||
| 260 | extent_deactivate_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, | ||
| 261 | edata_t *edata) { | ||
| 262 | assert(edata_state_get(edata) == extent_state_active); | ||
| 263 | extent_deactivate_locked_impl(tsdn, pac, ecache, edata); | ||
| 264 | } | ||
| 265 | |||
| 266 | static void | ||
| 267 | extent_deactivate_check_state_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, | ||
| 268 | edata_t *edata, extent_state_t expected_state) { | ||
| 269 | assert(edata_state_get(edata) == expected_state); | ||
| 270 | extent_deactivate_locked_impl(tsdn, pac, ecache, edata); | ||
| 271 | } | ||
| 272 | |||
| 273 | static void | ||
| 274 | extent_activate_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, eset_t *eset, | ||
| 275 | edata_t *edata) { | ||
| 276 | assert(edata_arena_ind_get(edata) == ecache_ind_get(ecache)); | ||
| 277 | assert(edata_state_get(edata) == ecache->state || | ||
| 278 | edata_state_get(edata) == extent_state_merging); | ||
| 279 | |||
| 280 | eset_remove(eset, edata); | ||
| 281 | emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active); | ||
| 282 | } | ||
| 283 | |||
| 284 | void | ||
| 285 | extent_gdump_add(tsdn_t *tsdn, const edata_t *edata) { | ||
| 286 | cassert(config_prof); | ||
| 287 | /* prof_gdump() requirement. */ | ||
| 288 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 289 | WITNESS_RANK_CORE, 0); | ||
| 290 | |||
| 291 | if (opt_prof && edata_state_get(edata) == extent_state_active) { | ||
| 292 | size_t nadd = edata_size_get(edata) >> LG_PAGE; | ||
| 293 | size_t cur = atomic_fetch_add_zu(&curpages, nadd, | ||
| 294 | ATOMIC_RELAXED) + nadd; | ||
| 295 | size_t high = atomic_load_zu(&highpages, ATOMIC_RELAXED); | ||
| 296 | while (cur > high && !atomic_compare_exchange_weak_zu( | ||
| 297 | &highpages, &high, cur, ATOMIC_RELAXED, ATOMIC_RELAXED)) { | ||
| 298 | /* | ||
| 299 | * Don't refresh cur, because it may have decreased | ||
| 300 | * since this thread lost the highpages update race. | ||
| 301 | * Note that high is updated in case of CAS failure. | ||
| 302 | */ | ||
| 303 | } | ||
| 304 | if (cur > high && prof_gdump_get_unlocked()) { | ||
| 305 | prof_gdump(tsdn); | ||
| 306 | } | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | static void | ||
| 311 | extent_gdump_sub(tsdn_t *tsdn, const edata_t *edata) { | ||
| 312 | cassert(config_prof); | ||
| 313 | |||
| 314 | if (opt_prof && edata_state_get(edata) == extent_state_active) { | ||
| 315 | size_t nsub = edata_size_get(edata) >> LG_PAGE; | ||
| 316 | assert(atomic_load_zu(&curpages, ATOMIC_RELAXED) >= nsub); | ||
| 317 | atomic_fetch_sub_zu(&curpages, nsub, ATOMIC_RELAXED); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | static bool | ||
| 322 | extent_register_impl(tsdn_t *tsdn, pac_t *pac, edata_t *edata, bool gdump_add) { | ||
| 323 | assert(edata_state_get(edata) == extent_state_active); | ||
| 324 | /* | ||
| 325 | * No locking needed, as the edata must be in active state, which | ||
| 326 | * prevents other threads from accessing the edata. | ||
| 327 | */ | ||
| 328 | if (emap_register_boundary(tsdn, pac->emap, edata, SC_NSIZES, | ||
| 329 | /* slab */ false)) { | ||
| 330 | return true; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (config_prof && gdump_add) { | ||
| 334 | extent_gdump_add(tsdn, edata); | ||
| 335 | } | ||
| 336 | |||
| 337 | return false; | ||
| 338 | } | ||
| 339 | |||
| 340 | static bool | ||
| 341 | extent_register(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { | ||
| 342 | return extent_register_impl(tsdn, pac, edata, true); | ||
| 343 | } | ||
| 344 | |||
| 345 | static bool | ||
| 346 | extent_register_no_gdump_add(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { | ||
| 347 | return extent_register_impl(tsdn, pac, edata, false); | ||
| 348 | } | ||
| 349 | |||
| 350 | static void | ||
| 351 | extent_reregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { | ||
| 352 | bool err = extent_register(tsdn, pac, edata); | ||
| 353 | assert(!err); | ||
| 354 | } | ||
| 355 | |||
| 356 | /* | ||
| 357 | * Removes all pointers to the given extent from the global rtree. | ||
| 358 | */ | ||
| 359 | static void | ||
| 360 | extent_deregister_impl(tsdn_t *tsdn, pac_t *pac, edata_t *edata, | ||
| 361 | bool gdump) { | ||
| 362 | emap_deregister_boundary(tsdn, pac->emap, edata); | ||
| 363 | |||
| 364 | if (config_prof && gdump) { | ||
| 365 | extent_gdump_sub(tsdn, edata); | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | static void | ||
| 370 | extent_deregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata) { | ||
| 371 | extent_deregister_impl(tsdn, pac, edata, true); | ||
| 372 | } | ||
| 373 | |||
| 374 | static void | ||
| 375 | extent_deregister_no_gdump_sub(tsdn_t *tsdn, pac_t *pac, | ||
| 376 | edata_t *edata) { | ||
| 377 | extent_deregister_impl(tsdn, pac, edata, false); | ||
| 378 | } | ||
| 379 | |||
| 380 | /* | ||
| 381 | * Tries to find and remove an extent from ecache that can be used for the | ||
| 382 | * given allocation request. | ||
| 383 | */ | ||
| 384 | static edata_t * | ||
| 385 | extent_recycle_extract(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 386 | ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, | ||
| 387 | bool guarded) { | ||
| 388 | malloc_mutex_assert_owner(tsdn, &ecache->mtx); | ||
| 389 | assert(alignment > 0); | ||
| 390 | if (config_debug && expand_edata != NULL) { | ||
| 391 | /* | ||
| 392 | * Non-NULL expand_edata indicates in-place expanding realloc. | ||
| 393 | * new_addr must either refer to a non-existing extent, or to | ||
| 394 | * the base of an extant extent, since only active slabs support | ||
| 395 | * interior lookups (which of course cannot be recycled). | ||
| 396 | */ | ||
| 397 | void *new_addr = edata_past_get(expand_edata); | ||
| 398 | assert(PAGE_ADDR2BASE(new_addr) == new_addr); | ||
| 399 | assert(alignment <= PAGE); | ||
| 400 | } | ||
| 401 | |||
| 402 | edata_t *edata; | ||
| 403 | eset_t *eset = guarded ? &ecache->guarded_eset : &ecache->eset; | ||
| 404 | if (expand_edata != NULL) { | ||
| 405 | edata = emap_try_acquire_edata_neighbor_expand(tsdn, pac->emap, | ||
| 406 | expand_edata, EXTENT_PAI_PAC, ecache->state); | ||
| 407 | if (edata != NULL) { | ||
| 408 | extent_assert_can_expand(expand_edata, edata); | ||
| 409 | if (edata_size_get(edata) < size) { | ||
| 410 | emap_release_edata(tsdn, pac->emap, edata, | ||
| 411 | ecache->state); | ||
| 412 | edata = NULL; | ||
| 413 | } | ||
| 414 | } | ||
| 415 | } else { | ||
| 416 | /* | ||
| 417 | * A large extent might be broken up from its original size to | ||
| 418 | * some small size to satisfy a small request. When that small | ||
| 419 | * request is freed, though, it won't merge back with the larger | ||
| 420 | * extent if delayed coalescing is on. The large extent can | ||
| 421 | * then no longer satify a request for its original size. To | ||
| 422 | * limit this effect, when delayed coalescing is enabled, we | ||
| 423 | * put a cap on how big an extent we can split for a request. | ||
| 424 | */ | ||
| 425 | unsigned lg_max_fit = ecache->delay_coalesce | ||
| 426 | ? (unsigned)opt_lg_extent_max_active_fit : SC_PTR_BITS; | ||
| 427 | |||
| 428 | /* | ||
| 429 | * If split and merge are not allowed (Windows w/o retain), try | ||
| 430 | * exact fit only. | ||
| 431 | * | ||
| 432 | * For simplicity purposes, splitting guarded extents is not | ||
| 433 | * supported. Hence, we do only exact fit for guarded | ||
| 434 | * allocations. | ||
| 435 | */ | ||
| 436 | bool exact_only = (!maps_coalesce && !opt_retain) || guarded; | ||
| 437 | edata = eset_fit(eset, size, alignment, exact_only, | ||
| 438 | lg_max_fit); | ||
| 439 | } | ||
| 440 | if (edata == NULL) { | ||
| 441 | return NULL; | ||
| 442 | } | ||
| 443 | assert(!guarded || edata_guarded_get(edata)); | ||
| 444 | extent_activate_locked(tsdn, pac, ecache, eset, edata); | ||
| 445 | |||
| 446 | return edata; | ||
| 447 | } | ||
| 448 | |||
| 449 | /* | ||
| 450 | * Given an allocation request and an extent guaranteed to be able to satisfy | ||
| 451 | * it, this splits off lead and trail extents, leaving edata pointing to an | ||
| 452 | * extent satisfying the allocation. | ||
| 453 | * This function doesn't put lead or trail into any ecache; it's the caller's | ||
| 454 | * job to ensure that they can be reused. | ||
| 455 | */ | ||
| 456 | typedef enum { | ||
| 457 | /* | ||
| 458 | * Split successfully. lead, edata, and trail, are modified to extents | ||
| 459 | * describing the ranges before, in, and after the given allocation. | ||
| 460 | */ | ||
| 461 | extent_split_interior_ok, | ||
| 462 | /* | ||
| 463 | * The extent can't satisfy the given allocation request. None of the | ||
| 464 | * input edata_t *s are touched. | ||
| 465 | */ | ||
| 466 | extent_split_interior_cant_alloc, | ||
| 467 | /* | ||
| 468 | * In a potentially invalid state. Must leak (if *to_leak is non-NULL), | ||
| 469 | * and salvage what's still salvageable (if *to_salvage is non-NULL). | ||
| 470 | * None of lead, edata, or trail are valid. | ||
| 471 | */ | ||
| 472 | extent_split_interior_error | ||
| 473 | } extent_split_interior_result_t; | ||
| 474 | |||
| 475 | static extent_split_interior_result_t | ||
| 476 | extent_split_interior(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 477 | /* The result of splitting, in case of success. */ | ||
| 478 | edata_t **edata, edata_t **lead, edata_t **trail, | ||
| 479 | /* The mess to clean up, in case of error. */ | ||
| 480 | edata_t **to_leak, edata_t **to_salvage, | ||
| 481 | edata_t *expand_edata, size_t size, size_t alignment) { | ||
| 482 | size_t leadsize = ALIGNMENT_CEILING((uintptr_t)edata_base_get(*edata), | ||
| 483 | PAGE_CEILING(alignment)) - (uintptr_t)edata_base_get(*edata); | ||
| 484 | assert(expand_edata == NULL || leadsize == 0); | ||
| 485 | if (edata_size_get(*edata) < leadsize + size) { | ||
| 486 | return extent_split_interior_cant_alloc; | ||
| 487 | } | ||
| 488 | size_t trailsize = edata_size_get(*edata) - leadsize - size; | ||
| 489 | |||
| 490 | *lead = NULL; | ||
| 491 | *trail = NULL; | ||
| 492 | *to_leak = NULL; | ||
| 493 | *to_salvage = NULL; | ||
| 494 | |||
| 495 | /* Split the lead. */ | ||
| 496 | if (leadsize != 0) { | ||
| 497 | assert(!edata_guarded_get(*edata)); | ||
| 498 | *lead = *edata; | ||
| 499 | *edata = extent_split_impl(tsdn, pac, ehooks, *lead, leadsize, | ||
| 500 | size + trailsize, /* holding_core_locks*/ true); | ||
| 501 | if (*edata == NULL) { | ||
| 502 | *to_leak = *lead; | ||
| 503 | *lead = NULL; | ||
| 504 | return extent_split_interior_error; | ||
| 505 | } | ||
| 506 | } | ||
| 507 | |||
| 508 | /* Split the trail. */ | ||
| 509 | if (trailsize != 0) { | ||
| 510 | assert(!edata_guarded_get(*edata)); | ||
| 511 | *trail = extent_split_impl(tsdn, pac, ehooks, *edata, size, | ||
| 512 | trailsize, /* holding_core_locks */ true); | ||
| 513 | if (*trail == NULL) { | ||
| 514 | *to_leak = *edata; | ||
| 515 | *to_salvage = *lead; | ||
| 516 | *lead = NULL; | ||
| 517 | *edata = NULL; | ||
| 518 | return extent_split_interior_error; | ||
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | return extent_split_interior_ok; | ||
| 523 | } | ||
| 524 | |||
| 525 | /* | ||
| 526 | * This fulfills the indicated allocation request out of the given extent (which | ||
| 527 | * the caller should have ensured was big enough). If there's any unused space | ||
| 528 | * before or after the resulting allocation, that space is given its own extent | ||
| 529 | * and put back into ecache. | ||
| 530 | */ | ||
| 531 | static edata_t * | ||
| 532 | extent_recycle_split(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 533 | ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment, | ||
| 534 | edata_t *edata, bool growing_retained) { | ||
| 535 | assert(!edata_guarded_get(edata) || size == edata_size_get(edata)); | ||
| 536 | malloc_mutex_assert_owner(tsdn, &ecache->mtx); | ||
| 537 | |||
| 538 | edata_t *lead; | ||
| 539 | edata_t *trail; | ||
| 540 | edata_t *to_leak JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 541 | edata_t *to_salvage JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 542 | |||
| 543 | extent_split_interior_result_t result = extent_split_interior( | ||
| 544 | tsdn, pac, ehooks, &edata, &lead, &trail, &to_leak, &to_salvage, | ||
| 545 | expand_edata, size, alignment); | ||
| 546 | |||
| 547 | if (!maps_coalesce && result != extent_split_interior_ok | ||
| 548 | && !opt_retain) { | ||
| 549 | /* | ||
| 550 | * Split isn't supported (implies Windows w/o retain). Avoid | ||
| 551 | * leaking the extent. | ||
| 552 | */ | ||
| 553 | assert(to_leak != NULL && lead == NULL && trail == NULL); | ||
| 554 | extent_deactivate_locked(tsdn, pac, ecache, to_leak); | ||
| 555 | return NULL; | ||
| 556 | } | ||
| 557 | |||
| 558 | if (result == extent_split_interior_ok) { | ||
| 559 | if (lead != NULL) { | ||
| 560 | extent_deactivate_locked(tsdn, pac, ecache, lead); | ||
| 561 | } | ||
| 562 | if (trail != NULL) { | ||
| 563 | extent_deactivate_locked(tsdn, pac, ecache, trail); | ||
| 564 | } | ||
| 565 | return edata; | ||
| 566 | } else { | ||
| 567 | /* | ||
| 568 | * We should have picked an extent that was large enough to | ||
| 569 | * fulfill our allocation request. | ||
| 570 | */ | ||
| 571 | assert(result == extent_split_interior_error); | ||
| 572 | if (to_salvage != NULL) { | ||
| 573 | extent_deregister(tsdn, pac, to_salvage); | ||
| 574 | } | ||
| 575 | if (to_leak != NULL) { | ||
| 576 | extent_deregister_no_gdump_sub(tsdn, pac, to_leak); | ||
| 577 | /* | ||
| 578 | * May go down the purge path (which assume no ecache | ||
| 579 | * locks). Only happens with OOM caused split failures. | ||
| 580 | */ | ||
| 581 | malloc_mutex_unlock(tsdn, &ecache->mtx); | ||
| 582 | extents_abandon_vm(tsdn, pac, ehooks, ecache, to_leak, | ||
| 583 | growing_retained); | ||
| 584 | malloc_mutex_lock(tsdn, &ecache->mtx); | ||
| 585 | } | ||
| 586 | return NULL; | ||
| 587 | } | ||
| 588 | unreachable(); | ||
| 589 | } | ||
| 590 | |||
| 591 | /* | ||
| 592 | * Tries to satisfy the given allocation request by reusing one of the extents | ||
| 593 | * in the given ecache_t. | ||
| 594 | */ | ||
| 595 | static edata_t * | ||
| 596 | extent_recycle(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 597 | edata_t *expand_edata, size_t size, size_t alignment, bool zero, | ||
| 598 | bool *commit, bool growing_retained, bool guarded) { | ||
| 599 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 600 | WITNESS_RANK_CORE, growing_retained ? 1 : 0); | ||
| 601 | assert(!guarded || expand_edata == NULL); | ||
| 602 | assert(!guarded || alignment <= PAGE); | ||
| 603 | |||
| 604 | malloc_mutex_lock(tsdn, &ecache->mtx); | ||
| 605 | |||
| 606 | edata_t *edata = extent_recycle_extract(tsdn, pac, ehooks, ecache, | ||
| 607 | expand_edata, size, alignment, guarded); | ||
| 608 | if (edata == NULL) { | ||
| 609 | malloc_mutex_unlock(tsdn, &ecache->mtx); | ||
| 610 | return NULL; | ||
| 611 | } | ||
| 612 | |||
| 613 | edata = extent_recycle_split(tsdn, pac, ehooks, ecache, expand_edata, | ||
| 614 | size, alignment, edata, growing_retained); | ||
| 615 | malloc_mutex_unlock(tsdn, &ecache->mtx); | ||
| 616 | if (edata == NULL) { | ||
| 617 | return NULL; | ||
| 618 | } | ||
| 619 | |||
| 620 | assert(edata_state_get(edata) == extent_state_active); | ||
| 621 | if (extent_commit_zero(tsdn, ehooks, edata, *commit, zero, | ||
| 622 | growing_retained)) { | ||
| 623 | extent_record(tsdn, pac, ehooks, ecache, edata); | ||
| 624 | return NULL; | ||
| 625 | } | ||
| 626 | if (edata_committed_get(edata)) { | ||
| 627 | /* | ||
| 628 | * This reverses the purpose of this variable - previously it | ||
| 629 | * was treated as an input parameter, now it turns into an | ||
| 630 | * output parameter, reporting if the edata has actually been | ||
| 631 | * committed. | ||
| 632 | */ | ||
| 633 | *commit = true; | ||
| 634 | } | ||
| 635 | return edata; | ||
| 636 | } | ||
| 637 | |||
| 638 | /* | ||
| 639 | * If virtual memory is retained, create increasingly larger extents from which | ||
| 640 | * to split requested extents in order to limit the total number of disjoint | ||
| 641 | * virtual memory ranges retained by each shard. | ||
| 642 | */ | ||
| 643 | static edata_t * | ||
| 644 | extent_grow_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 645 | size_t size, size_t alignment, bool zero, bool *commit) { | ||
| 646 | malloc_mutex_assert_owner(tsdn, &pac->grow_mtx); | ||
| 647 | |||
| 648 | size_t alloc_size_min = size + PAGE_CEILING(alignment) - PAGE; | ||
| 649 | /* Beware size_t wrap-around. */ | ||
| 650 | if (alloc_size_min < size) { | ||
| 651 | goto label_err; | ||
| 652 | } | ||
| 653 | /* | ||
| 654 | * Find the next extent size in the series that would be large enough to | ||
| 655 | * satisfy this request. | ||
| 656 | */ | ||
| 657 | size_t alloc_size; | ||
| 658 | pszind_t exp_grow_skip; | ||
| 659 | bool err = exp_grow_size_prepare(&pac->exp_grow, alloc_size_min, | ||
| 660 | &alloc_size, &exp_grow_skip); | ||
| 661 | if (err) { | ||
| 662 | goto label_err; | ||
| 663 | } | ||
| 664 | |||
| 665 | edata_t *edata = edata_cache_get(tsdn, pac->edata_cache); | ||
| 666 | if (edata == NULL) { | ||
| 667 | goto label_err; | ||
| 668 | } | ||
| 669 | bool zeroed = false; | ||
| 670 | bool committed = false; | ||
| 671 | |||
| 672 | void *ptr = ehooks_alloc(tsdn, ehooks, NULL, alloc_size, PAGE, &zeroed, | ||
| 673 | &committed); | ||
| 674 | |||
| 675 | if (ptr == NULL) { | ||
| 676 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 677 | goto label_err; | ||
| 678 | } | ||
| 679 | |||
| 680 | edata_init(edata, ecache_ind_get(&pac->ecache_retained), ptr, | ||
| 681 | alloc_size, false, SC_NSIZES, extent_sn_next(pac), | ||
| 682 | extent_state_active, zeroed, committed, EXTENT_PAI_PAC, | ||
| 683 | EXTENT_IS_HEAD); | ||
| 684 | |||
| 685 | if (extent_register_no_gdump_add(tsdn, pac, edata)) { | ||
| 686 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 687 | goto label_err; | ||
| 688 | } | ||
| 689 | |||
| 690 | if (edata_committed_get(edata)) { | ||
| 691 | *commit = true; | ||
| 692 | } | ||
| 693 | |||
| 694 | edata_t *lead; | ||
| 695 | edata_t *trail; | ||
| 696 | edata_t *to_leak JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 697 | edata_t *to_salvage JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 698 | |||
| 699 | extent_split_interior_result_t result = extent_split_interior(tsdn, | ||
| 700 | pac, ehooks, &edata, &lead, &trail, &to_leak, &to_salvage, NULL, | ||
| 701 | size, alignment); | ||
| 702 | |||
| 703 | if (result == extent_split_interior_ok) { | ||
| 704 | if (lead != NULL) { | ||
| 705 | extent_record(tsdn, pac, ehooks, &pac->ecache_retained, | ||
| 706 | lead); | ||
| 707 | } | ||
| 708 | if (trail != NULL) { | ||
| 709 | extent_record(tsdn, pac, ehooks, &pac->ecache_retained, | ||
| 710 | trail); | ||
| 711 | } | ||
| 712 | } else { | ||
| 713 | /* | ||
| 714 | * We should have allocated a sufficiently large extent; the | ||
| 715 | * cant_alloc case should not occur. | ||
| 716 | */ | ||
| 717 | assert(result == extent_split_interior_error); | ||
| 718 | if (to_salvage != NULL) { | ||
| 719 | if (config_prof) { | ||
| 720 | extent_gdump_add(tsdn, to_salvage); | ||
| 721 | } | ||
| 722 | extent_record(tsdn, pac, ehooks, &pac->ecache_retained, | ||
| 723 | to_salvage); | ||
| 724 | } | ||
| 725 | if (to_leak != NULL) { | ||
| 726 | extent_deregister_no_gdump_sub(tsdn, pac, to_leak); | ||
| 727 | extents_abandon_vm(tsdn, pac, ehooks, | ||
| 728 | &pac->ecache_retained, to_leak, true); | ||
| 729 | } | ||
| 730 | goto label_err; | ||
| 731 | } | ||
| 732 | |||
| 733 | if (*commit && !edata_committed_get(edata)) { | ||
| 734 | if (extent_commit_impl(tsdn, ehooks, edata, 0, | ||
| 735 | edata_size_get(edata), true)) { | ||
| 736 | extent_record(tsdn, pac, ehooks, | ||
| 737 | &pac->ecache_retained, edata); | ||
| 738 | goto label_err; | ||
| 739 | } | ||
| 740 | /* A successful commit should return zeroed memory. */ | ||
| 741 | if (config_debug) { | ||
| 742 | void *addr = edata_addr_get(edata); | ||
| 743 | size_t *p = (size_t *)(uintptr_t)addr; | ||
| 744 | /* Check the first page only. */ | ||
| 745 | for (size_t i = 0; i < PAGE / sizeof(size_t); i++) { | ||
| 746 | assert(p[i] == 0); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | /* | ||
| 752 | * Increment extent_grow_next if doing so wouldn't exceed the allowed | ||
| 753 | * range. | ||
| 754 | */ | ||
| 755 | /* All opportunities for failure are past. */ | ||
| 756 | exp_grow_size_commit(&pac->exp_grow, exp_grow_skip); | ||
| 757 | malloc_mutex_unlock(tsdn, &pac->grow_mtx); | ||
| 758 | |||
| 759 | if (config_prof) { | ||
| 760 | /* Adjust gdump stats now that extent is final size. */ | ||
| 761 | extent_gdump_add(tsdn, edata); | ||
| 762 | } | ||
| 763 | if (zero && !edata_zeroed_get(edata)) { | ||
| 764 | ehooks_zero(tsdn, ehooks, edata_base_get(edata), | ||
| 765 | edata_size_get(edata)); | ||
| 766 | } | ||
| 767 | return edata; | ||
| 768 | label_err: | ||
| 769 | malloc_mutex_unlock(tsdn, &pac->grow_mtx); | ||
| 770 | return NULL; | ||
| 771 | } | ||
| 772 | |||
| 773 | static edata_t * | ||
| 774 | extent_alloc_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 775 | edata_t *expand_edata, size_t size, size_t alignment, bool zero, | ||
| 776 | bool *commit, bool guarded) { | ||
| 777 | assert(size != 0); | ||
| 778 | assert(alignment != 0); | ||
| 779 | |||
| 780 | malloc_mutex_lock(tsdn, &pac->grow_mtx); | ||
| 781 | |||
| 782 | edata_t *edata = extent_recycle(tsdn, pac, ehooks, | ||
| 783 | &pac->ecache_retained, expand_edata, size, alignment, zero, commit, | ||
| 784 | /* growing_retained */ true, guarded); | ||
| 785 | if (edata != NULL) { | ||
| 786 | malloc_mutex_unlock(tsdn, &pac->grow_mtx); | ||
| 787 | if (config_prof) { | ||
| 788 | extent_gdump_add(tsdn, edata); | ||
| 789 | } | ||
| 790 | } else if (opt_retain && expand_edata == NULL && !guarded) { | ||
| 791 | edata = extent_grow_retained(tsdn, pac, ehooks, size, | ||
| 792 | alignment, zero, commit); | ||
| 793 | /* extent_grow_retained() always releases pac->grow_mtx. */ | ||
| 794 | } else { | ||
| 795 | malloc_mutex_unlock(tsdn, &pac->grow_mtx); | ||
| 796 | } | ||
| 797 | malloc_mutex_assert_not_owner(tsdn, &pac->grow_mtx); | ||
| 798 | |||
| 799 | return edata; | ||
| 800 | } | ||
| 801 | |||
| 802 | static bool | ||
| 803 | extent_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 804 | edata_t *inner, edata_t *outer, bool forward) { | ||
| 805 | extent_assert_can_coalesce(inner, outer); | ||
| 806 | eset_remove(&ecache->eset, outer); | ||
| 807 | |||
| 808 | bool err = extent_merge_impl(tsdn, pac, ehooks, | ||
| 809 | forward ? inner : outer, forward ? outer : inner, | ||
| 810 | /* holding_core_locks */ true); | ||
| 811 | if (err) { | ||
| 812 | extent_deactivate_check_state_locked(tsdn, pac, ecache, outer, | ||
| 813 | extent_state_merging); | ||
| 814 | } | ||
| 815 | |||
| 816 | return err; | ||
| 817 | } | ||
| 818 | |||
| 819 | static edata_t * | ||
| 820 | extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 821 | ecache_t *ecache, edata_t *edata, bool *coalesced) { | ||
| 822 | assert(!edata_guarded_get(edata)); | ||
| 823 | /* | ||
| 824 | * We avoid checking / locking inactive neighbors for large size | ||
| 825 | * classes, since they are eagerly coalesced on deallocation which can | ||
| 826 | * cause lock contention. | ||
| 827 | */ | ||
| 828 | /* | ||
| 829 | * Continue attempting to coalesce until failure, to protect against | ||
| 830 | * races with other threads that are thwarted by this one. | ||
| 831 | */ | ||
| 832 | bool again; | ||
| 833 | do { | ||
| 834 | again = false; | ||
| 835 | |||
| 836 | /* Try to coalesce forward. */ | ||
| 837 | edata_t *next = emap_try_acquire_edata_neighbor(tsdn, pac->emap, | ||
| 838 | edata, EXTENT_PAI_PAC, ecache->state, /* forward */ true); | ||
| 839 | if (next != NULL) { | ||
| 840 | if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata, | ||
| 841 | next, true)) { | ||
| 842 | if (ecache->delay_coalesce) { | ||
| 843 | /* Do minimal coalescing. */ | ||
| 844 | *coalesced = true; | ||
| 845 | return edata; | ||
| 846 | } | ||
| 847 | again = true; | ||
| 848 | } | ||
| 849 | } | ||
| 850 | |||
| 851 | /* Try to coalesce backward. */ | ||
| 852 | edata_t *prev = emap_try_acquire_edata_neighbor(tsdn, pac->emap, | ||
| 853 | edata, EXTENT_PAI_PAC, ecache->state, /* forward */ false); | ||
| 854 | if (prev != NULL) { | ||
| 855 | if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata, | ||
| 856 | prev, false)) { | ||
| 857 | edata = prev; | ||
| 858 | if (ecache->delay_coalesce) { | ||
| 859 | /* Do minimal coalescing. */ | ||
| 860 | *coalesced = true; | ||
| 861 | return edata; | ||
| 862 | } | ||
| 863 | again = true; | ||
| 864 | } | ||
| 865 | } | ||
| 866 | } while (again); | ||
| 867 | |||
| 868 | if (ecache->delay_coalesce) { | ||
| 869 | *coalesced = false; | ||
| 870 | } | ||
| 871 | return edata; | ||
| 872 | } | ||
| 873 | |||
| 874 | static edata_t * | ||
| 875 | extent_try_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 876 | ecache_t *ecache, edata_t *edata, bool *coalesced) { | ||
| 877 | return extent_try_coalesce_impl(tsdn, pac, ehooks, ecache, edata, | ||
| 878 | coalesced); | ||
| 879 | } | ||
| 880 | |||
| 881 | static edata_t * | ||
| 882 | extent_try_coalesce_large(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 883 | ecache_t *ecache, edata_t *edata, bool *coalesced) { | ||
| 884 | return extent_try_coalesce_impl(tsdn, pac, ehooks, ecache, edata, | ||
| 885 | coalesced); | ||
| 886 | } | ||
| 887 | |||
| 888 | /* Purge a single extent to retained / unmapped directly. */ | ||
| 889 | static void | ||
| 890 | extent_maximally_purge(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 891 | edata_t *edata) { | ||
| 892 | size_t extent_size = edata_size_get(edata); | ||
| 893 | extent_dalloc_wrapper(tsdn, pac, ehooks, edata); | ||
| 894 | if (config_stats) { | ||
| 895 | /* Update stats accordingly. */ | ||
| 896 | LOCKEDINT_MTX_LOCK(tsdn, *pac->stats_mtx); | ||
| 897 | locked_inc_u64(tsdn, | ||
| 898 | LOCKEDINT_MTX(*pac->stats_mtx), | ||
| 899 | &pac->stats->decay_dirty.nmadvise, 1); | ||
| 900 | locked_inc_u64(tsdn, | ||
| 901 | LOCKEDINT_MTX(*pac->stats_mtx), | ||
| 902 | &pac->stats->decay_dirty.purged, | ||
| 903 | extent_size >> LG_PAGE); | ||
| 904 | LOCKEDINT_MTX_UNLOCK(tsdn, *pac->stats_mtx); | ||
| 905 | atomic_fetch_sub_zu(&pac->stats->pac_mapped, extent_size, | ||
| 906 | ATOMIC_RELAXED); | ||
| 907 | } | ||
| 908 | } | ||
| 909 | |||
| 910 | /* | ||
| 911 | * Does the metadata management portions of putting an unused extent into the | ||
| 912 | * given ecache_t (coalesces and inserts into the eset). | ||
| 913 | */ | ||
| 914 | void | ||
| 915 | extent_record(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache, | ||
| 916 | edata_t *edata) { | ||
| 917 | assert((ecache->state != extent_state_dirty && | ||
| 918 | ecache->state != extent_state_muzzy) || | ||
| 919 | !edata_zeroed_get(edata)); | ||
| 920 | |||
| 921 | malloc_mutex_lock(tsdn, &ecache->mtx); | ||
| 922 | |||
| 923 | emap_assert_mapped(tsdn, pac->emap, edata); | ||
| 924 | |||
| 925 | if (edata_guarded_get(edata)) { | ||
| 926 | goto label_skip_coalesce; | ||
| 927 | } | ||
| 928 | if (!ecache->delay_coalesce) { | ||
| 929 | edata = extent_try_coalesce(tsdn, pac, ehooks, ecache, edata, | ||
| 930 | NULL); | ||
| 931 | } else if (edata_size_get(edata) >= SC_LARGE_MINCLASS) { | ||
| 932 | assert(ecache == &pac->ecache_dirty); | ||
| 933 | /* Always coalesce large extents eagerly. */ | ||
| 934 | bool coalesced; | ||
| 935 | do { | ||
| 936 | assert(edata_state_get(edata) == extent_state_active); | ||
| 937 | edata = extent_try_coalesce_large(tsdn, pac, ehooks, | ||
| 938 | ecache, edata, &coalesced); | ||
| 939 | } while (coalesced); | ||
| 940 | if (edata_size_get(edata) >= | ||
| 941 | atomic_load_zu(&pac->oversize_threshold, ATOMIC_RELAXED) | ||
| 942 | && extent_may_force_decay(pac)) { | ||
| 943 | /* Shortcut to purge the oversize extent eagerly. */ | ||
| 944 | malloc_mutex_unlock(tsdn, &ecache->mtx); | ||
| 945 | extent_maximally_purge(tsdn, pac, ehooks, edata); | ||
| 946 | return; | ||
| 947 | } | ||
| 948 | } | ||
| 949 | label_skip_coalesce: | ||
| 950 | extent_deactivate_locked(tsdn, pac, ecache, edata); | ||
| 951 | |||
| 952 | malloc_mutex_unlock(tsdn, &ecache->mtx); | ||
| 953 | } | ||
| 954 | |||
| 955 | void | ||
| 956 | extent_dalloc_gap(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 957 | edata_t *edata) { | ||
| 958 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 959 | WITNESS_RANK_CORE, 0); | ||
| 960 | |||
| 961 | if (extent_register(tsdn, pac, edata)) { | ||
| 962 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 963 | return; | ||
| 964 | } | ||
| 965 | extent_dalloc_wrapper(tsdn, pac, ehooks, edata); | ||
| 966 | } | ||
| 967 | |||
| 968 | static bool | ||
| 969 | extent_dalloc_wrapper_try(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 970 | edata_t *edata) { | ||
| 971 | bool err; | ||
| 972 | |||
| 973 | assert(edata_base_get(edata) != NULL); | ||
| 974 | assert(edata_size_get(edata) != 0); | ||
| 975 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 976 | WITNESS_RANK_CORE, 0); | ||
| 977 | |||
| 978 | edata_addr_set(edata, edata_base_get(edata)); | ||
| 979 | |||
| 980 | /* Try to deallocate. */ | ||
| 981 | err = ehooks_dalloc(tsdn, ehooks, edata_base_get(edata), | ||
| 982 | edata_size_get(edata), edata_committed_get(edata)); | ||
| 983 | |||
| 984 | if (!err) { | ||
| 985 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 986 | } | ||
| 987 | |||
| 988 | return err; | ||
| 989 | } | ||
| 990 | |||
| 991 | edata_t * | ||
| 992 | extent_alloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 993 | void *new_addr, size_t size, size_t alignment, bool zero, bool *commit, | ||
| 994 | bool growing_retained) { | ||
| 995 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 996 | WITNESS_RANK_CORE, growing_retained ? 1 : 0); | ||
| 997 | |||
| 998 | edata_t *edata = edata_cache_get(tsdn, pac->edata_cache); | ||
| 999 | if (edata == NULL) { | ||
| 1000 | return NULL; | ||
| 1001 | } | ||
| 1002 | size_t palignment = ALIGNMENT_CEILING(alignment, PAGE); | ||
| 1003 | void *addr = ehooks_alloc(tsdn, ehooks, new_addr, size, palignment, | ||
| 1004 | &zero, commit); | ||
| 1005 | if (addr == NULL) { | ||
| 1006 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 1007 | return NULL; | ||
| 1008 | } | ||
| 1009 | edata_init(edata, ecache_ind_get(&pac->ecache_dirty), addr, | ||
| 1010 | size, /* slab */ false, SC_NSIZES, extent_sn_next(pac), | ||
| 1011 | extent_state_active, zero, *commit, EXTENT_PAI_PAC, | ||
| 1012 | opt_retain ? EXTENT_IS_HEAD : EXTENT_NOT_HEAD); | ||
| 1013 | /* | ||
| 1014 | * Retained memory is not counted towards gdump. Only if an extent is | ||
| 1015 | * allocated as a separate mapping, i.e. growing_retained is false, then | ||
| 1016 | * gdump should be updated. | ||
| 1017 | */ | ||
| 1018 | bool gdump_add = !growing_retained; | ||
| 1019 | if (extent_register_impl(tsdn, pac, edata, gdump_add)) { | ||
| 1020 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 1021 | return NULL; | ||
| 1022 | } | ||
| 1023 | |||
| 1024 | return edata; | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | void | ||
| 1028 | extent_dalloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 1029 | edata_t *edata) { | ||
| 1030 | assert(edata_pai_get(edata) == EXTENT_PAI_PAC); | ||
| 1031 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1032 | WITNESS_RANK_CORE, 0); | ||
| 1033 | |||
| 1034 | /* Avoid calling the default extent_dalloc unless have to. */ | ||
| 1035 | if (!ehooks_dalloc_will_fail(ehooks)) { | ||
| 1036 | /* Remove guard pages for dalloc / unmap. */ | ||
| 1037 | if (edata_guarded_get(edata)) { | ||
| 1038 | assert(ehooks_are_default(ehooks)); | ||
| 1039 | san_unguard_pages_two_sided(tsdn, ehooks, edata, | ||
| 1040 | pac->emap); | ||
| 1041 | } | ||
| 1042 | /* | ||
| 1043 | * Deregister first to avoid a race with other allocating | ||
| 1044 | * threads, and reregister if deallocation fails. | ||
| 1045 | */ | ||
| 1046 | extent_deregister(tsdn, pac, edata); | ||
| 1047 | if (!extent_dalloc_wrapper_try(tsdn, pac, ehooks, edata)) { | ||
| 1048 | return; | ||
| 1049 | } | ||
| 1050 | extent_reregister(tsdn, pac, edata); | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | /* Try to decommit; purge if that fails. */ | ||
| 1054 | bool zeroed; | ||
| 1055 | if (!edata_committed_get(edata)) { | ||
| 1056 | zeroed = true; | ||
| 1057 | } else if (!extent_decommit_wrapper(tsdn, ehooks, edata, 0, | ||
| 1058 | edata_size_get(edata))) { | ||
| 1059 | zeroed = true; | ||
| 1060 | } else if (!ehooks_purge_forced(tsdn, ehooks, edata_base_get(edata), | ||
| 1061 | edata_size_get(edata), 0, edata_size_get(edata))) { | ||
| 1062 | zeroed = true; | ||
| 1063 | } else if (edata_state_get(edata) == extent_state_muzzy || | ||
| 1064 | !ehooks_purge_lazy(tsdn, ehooks, edata_base_get(edata), | ||
| 1065 | edata_size_get(edata), 0, edata_size_get(edata))) { | ||
| 1066 | zeroed = false; | ||
| 1067 | } else { | ||
| 1068 | zeroed = false; | ||
| 1069 | } | ||
| 1070 | edata_zeroed_set(edata, zeroed); | ||
| 1071 | |||
| 1072 | if (config_prof) { | ||
| 1073 | extent_gdump_sub(tsdn, edata); | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | extent_record(tsdn, pac, ehooks, &pac->ecache_retained, edata); | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | void | ||
| 1080 | extent_destroy_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 1081 | edata_t *edata) { | ||
| 1082 | assert(edata_base_get(edata) != NULL); | ||
| 1083 | assert(edata_size_get(edata) != 0); | ||
| 1084 | extent_state_t state = edata_state_get(edata); | ||
| 1085 | assert(state == extent_state_retained || state == extent_state_active); | ||
| 1086 | assert(emap_edata_is_acquired(tsdn, pac->emap, edata)); | ||
| 1087 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1088 | WITNESS_RANK_CORE, 0); | ||
| 1089 | |||
| 1090 | if (edata_guarded_get(edata)) { | ||
| 1091 | assert(opt_retain); | ||
| 1092 | san_unguard_pages_pre_destroy(tsdn, ehooks, edata, pac->emap); | ||
| 1093 | } | ||
| 1094 | edata_addr_set(edata, edata_base_get(edata)); | ||
| 1095 | |||
| 1096 | /* Try to destroy; silently fail otherwise. */ | ||
| 1097 | ehooks_destroy(tsdn, ehooks, edata_base_get(edata), | ||
| 1098 | edata_size_get(edata), edata_committed_get(edata)); | ||
| 1099 | |||
| 1100 | edata_cache_put(tsdn, pac->edata_cache, edata); | ||
| 1101 | } | ||
| 1102 | |||
| 1103 | static bool | ||
| 1104 | extent_commit_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1105 | size_t offset, size_t length, bool growing_retained) { | ||
| 1106 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1107 | WITNESS_RANK_CORE, growing_retained ? 1 : 0); | ||
| 1108 | bool err = ehooks_commit(tsdn, ehooks, edata_base_get(edata), | ||
| 1109 | edata_size_get(edata), offset, length); | ||
| 1110 | edata_committed_set(edata, edata_committed_get(edata) || !err); | ||
| 1111 | return err; | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | bool | ||
| 1115 | extent_commit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1116 | size_t offset, size_t length) { | ||
| 1117 | return extent_commit_impl(tsdn, ehooks, edata, offset, length, | ||
| 1118 | /* growing_retained */ false); | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | bool | ||
| 1122 | extent_decommit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1123 | size_t offset, size_t length) { | ||
| 1124 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1125 | WITNESS_RANK_CORE, 0); | ||
| 1126 | bool err = ehooks_decommit(tsdn, ehooks, edata_base_get(edata), | ||
| 1127 | edata_size_get(edata), offset, length); | ||
| 1128 | edata_committed_set(edata, edata_committed_get(edata) && err); | ||
| 1129 | return err; | ||
| 1130 | } | ||
| 1131 | |||
| 1132 | static bool | ||
| 1133 | extent_purge_lazy_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1134 | size_t offset, size_t length, bool growing_retained) { | ||
| 1135 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1136 | WITNESS_RANK_CORE, growing_retained ? 1 : 0); | ||
| 1137 | bool err = ehooks_purge_lazy(tsdn, ehooks, edata_base_get(edata), | ||
| 1138 | edata_size_get(edata), offset, length); | ||
| 1139 | return err; | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | bool | ||
| 1143 | extent_purge_lazy_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1144 | size_t offset, size_t length) { | ||
| 1145 | return extent_purge_lazy_impl(tsdn, ehooks, edata, offset, | ||
| 1146 | length, false); | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | static bool | ||
| 1150 | extent_purge_forced_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1151 | size_t offset, size_t length, bool growing_retained) { | ||
| 1152 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1153 | WITNESS_RANK_CORE, growing_retained ? 1 : 0); | ||
| 1154 | bool err = ehooks_purge_forced(tsdn, ehooks, edata_base_get(edata), | ||
| 1155 | edata_size_get(edata), offset, length); | ||
| 1156 | return err; | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | bool | ||
| 1160 | extent_purge_forced_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1161 | size_t offset, size_t length) { | ||
| 1162 | return extent_purge_forced_impl(tsdn, ehooks, edata, offset, length, | ||
| 1163 | false); | ||
| 1164 | } | ||
| 1165 | |||
| 1166 | /* | ||
| 1167 | * Accepts the extent to split, and the characteristics of each side of the | ||
| 1168 | * split. The 'a' parameters go with the 'lead' of the resulting pair of | ||
| 1169 | * extents (the lower addressed portion of the split), and the 'b' parameters go | ||
| 1170 | * with the trail (the higher addressed portion). This makes 'extent' the lead, | ||
| 1171 | * and returns the trail (except in case of error). | ||
| 1172 | */ | ||
| 1173 | static edata_t * | ||
| 1174 | extent_split_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 1175 | edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks) { | ||
| 1176 | assert(edata_size_get(edata) == size_a + size_b); | ||
| 1177 | /* Only the shrink path may split w/o holding core locks. */ | ||
| 1178 | if (holding_core_locks) { | ||
| 1179 | witness_assert_positive_depth_to_rank( | ||
| 1180 | tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); | ||
| 1181 | } else { | ||
| 1182 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1183 | WITNESS_RANK_CORE, 0); | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | if (ehooks_split_will_fail(ehooks)) { | ||
| 1187 | return NULL; | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | edata_t *trail = edata_cache_get(tsdn, pac->edata_cache); | ||
| 1191 | if (trail == NULL) { | ||
| 1192 | goto label_error_a; | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | edata_init(trail, edata_arena_ind_get(edata), | ||
| 1196 | (void *)((uintptr_t)edata_base_get(edata) + size_a), size_b, | ||
| 1197 | /* slab */ false, SC_NSIZES, edata_sn_get(edata), | ||
| 1198 | edata_state_get(edata), edata_zeroed_get(edata), | ||
| 1199 | edata_committed_get(edata), EXTENT_PAI_PAC, EXTENT_NOT_HEAD); | ||
| 1200 | emap_prepare_t prepare; | ||
| 1201 | bool err = emap_split_prepare(tsdn, pac->emap, &prepare, edata, | ||
| 1202 | size_a, trail, size_b); | ||
| 1203 | if (err) { | ||
| 1204 | goto label_error_b; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | /* | ||
| 1208 | * No need to acquire trail or edata, because: 1) trail was new (just | ||
| 1209 | * allocated); and 2) edata is either an active allocation (the shrink | ||
| 1210 | * path), or in an acquired state (extracted from the ecache on the | ||
| 1211 | * extent_recycle_split path). | ||
| 1212 | */ | ||
| 1213 | assert(emap_edata_is_acquired(tsdn, pac->emap, edata)); | ||
| 1214 | assert(emap_edata_is_acquired(tsdn, pac->emap, trail)); | ||
| 1215 | |||
| 1216 | err = ehooks_split(tsdn, ehooks, edata_base_get(edata), size_a + size_b, | ||
| 1217 | size_a, size_b, edata_committed_get(edata)); | ||
| 1218 | |||
| 1219 | if (err) { | ||
| 1220 | goto label_error_b; | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | edata_size_set(edata, size_a); | ||
| 1224 | emap_split_commit(tsdn, pac->emap, &prepare, edata, size_a, trail, | ||
| 1225 | size_b); | ||
| 1226 | |||
| 1227 | return trail; | ||
| 1228 | label_error_b: | ||
| 1229 | edata_cache_put(tsdn, pac->edata_cache, trail); | ||
| 1230 | label_error_a: | ||
| 1231 | return NULL; | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | edata_t * | ||
| 1235 | extent_split_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata, | ||
| 1236 | size_t size_a, size_t size_b, bool holding_core_locks) { | ||
| 1237 | return extent_split_impl(tsdn, pac, ehooks, edata, size_a, size_b, | ||
| 1238 | holding_core_locks); | ||
| 1239 | } | ||
| 1240 | |||
| 1241 | static bool | ||
| 1242 | extent_merge_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *a, | ||
| 1243 | edata_t *b, bool holding_core_locks) { | ||
| 1244 | /* Only the expanding path may merge w/o holding ecache locks. */ | ||
| 1245 | if (holding_core_locks) { | ||
| 1246 | witness_assert_positive_depth_to_rank( | ||
| 1247 | tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE); | ||
| 1248 | } else { | ||
| 1249 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1250 | WITNESS_RANK_CORE, 0); | ||
| 1251 | } | ||
| 1252 | |||
| 1253 | assert(edata_base_get(a) < edata_base_get(b)); | ||
| 1254 | assert(edata_arena_ind_get(a) == edata_arena_ind_get(b)); | ||
| 1255 | assert(edata_arena_ind_get(a) == ehooks_ind_get(ehooks)); | ||
| 1256 | emap_assert_mapped(tsdn, pac->emap, a); | ||
| 1257 | emap_assert_mapped(tsdn, pac->emap, b); | ||
| 1258 | |||
| 1259 | bool err = ehooks_merge(tsdn, ehooks, edata_base_get(a), | ||
| 1260 | edata_size_get(a), edata_base_get(b), edata_size_get(b), | ||
| 1261 | edata_committed_get(a)); | ||
| 1262 | |||
| 1263 | if (err) { | ||
| 1264 | return true; | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | /* | ||
| 1268 | * The rtree writes must happen while all the relevant elements are | ||
| 1269 | * owned, so the following code uses decomposed helper functions rather | ||
| 1270 | * than extent_{,de}register() to do things in the right order. | ||
| 1271 | */ | ||
| 1272 | emap_prepare_t prepare; | ||
| 1273 | emap_merge_prepare(tsdn, pac->emap, &prepare, a, b); | ||
| 1274 | |||
| 1275 | assert(edata_state_get(a) == extent_state_active || | ||
| 1276 | edata_state_get(a) == extent_state_merging); | ||
| 1277 | edata_state_set(a, extent_state_active); | ||
| 1278 | edata_size_set(a, edata_size_get(a) + edata_size_get(b)); | ||
| 1279 | edata_sn_set(a, (edata_sn_get(a) < edata_sn_get(b)) ? | ||
| 1280 | edata_sn_get(a) : edata_sn_get(b)); | ||
| 1281 | edata_zeroed_set(a, edata_zeroed_get(a) && edata_zeroed_get(b)); | ||
| 1282 | |||
| 1283 | emap_merge_commit(tsdn, pac->emap, &prepare, a, b); | ||
| 1284 | |||
| 1285 | edata_cache_put(tsdn, pac->edata_cache, b); | ||
| 1286 | |||
| 1287 | return false; | ||
| 1288 | } | ||
| 1289 | |||
| 1290 | bool | ||
| 1291 | extent_merge_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, | ||
| 1292 | edata_t *a, edata_t *b) { | ||
| 1293 | return extent_merge_impl(tsdn, pac, ehooks, a, b, | ||
| 1294 | /* holding_core_locks */ false); | ||
| 1295 | } | ||
| 1296 | |||
| 1297 | bool | ||
| 1298 | extent_commit_zero(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 1299 | bool commit, bool zero, bool growing_retained) { | ||
| 1300 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 1301 | WITNESS_RANK_CORE, growing_retained ? 1 : 0); | ||
| 1302 | |||
| 1303 | if (commit && !edata_committed_get(edata)) { | ||
| 1304 | if (extent_commit_impl(tsdn, ehooks, edata, 0, | ||
| 1305 | edata_size_get(edata), growing_retained)) { | ||
| 1306 | return true; | ||
| 1307 | } | ||
| 1308 | } | ||
| 1309 | if (zero && !edata_zeroed_get(edata)) { | ||
| 1310 | void *addr = edata_base_get(edata); | ||
| 1311 | size_t size = edata_size_get(edata); | ||
| 1312 | ehooks_zero(tsdn, ehooks, addr, size); | ||
| 1313 | } | ||
| 1314 | return false; | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | bool | ||
| 1318 | extent_boot(void) { | ||
| 1319 | assert(sizeof(slab_data_t) >= sizeof(e_prof_info_t)); | ||
| 1320 | |||
| 1321 | if (have_dss) { | ||
| 1322 | extent_dss_boot(); | ||
| 1323 | } | ||
| 1324 | |||
| 1325 | return false; | ||
| 1326 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/extent_dss.c b/examples/redis-unstable/deps/jemalloc/src/extent_dss.c deleted file mode 100644 index 9a35bac..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/extent_dss.c +++ /dev/null | |||
| @@ -1,277 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/extent_dss.h" | ||
| 6 | #include "jemalloc/internal/spin.h" | ||
| 7 | |||
| 8 | /******************************************************************************/ | ||
| 9 | /* Data. */ | ||
| 10 | |||
| 11 | const char *opt_dss = DSS_DEFAULT; | ||
| 12 | |||
| 13 | const char *dss_prec_names[] = { | ||
| 14 | "disabled", | ||
| 15 | "primary", | ||
| 16 | "secondary", | ||
| 17 | "N/A" | ||
| 18 | }; | ||
| 19 | |||
| 20 | /* | ||
| 21 | * Current dss precedence default, used when creating new arenas. NB: This is | ||
| 22 | * stored as unsigned rather than dss_prec_t because in principle there's no | ||
| 23 | * guarantee that sizeof(dss_prec_t) is the same as sizeof(unsigned), and we use | ||
| 24 | * atomic operations to synchronize the setting. | ||
| 25 | */ | ||
| 26 | static atomic_u_t dss_prec_default = ATOMIC_INIT( | ||
| 27 | (unsigned)DSS_PREC_DEFAULT); | ||
| 28 | |||
| 29 | /* Base address of the DSS. */ | ||
| 30 | static void *dss_base; | ||
| 31 | /* Atomic boolean indicating whether a thread is currently extending DSS. */ | ||
| 32 | static atomic_b_t dss_extending; | ||
| 33 | /* Atomic boolean indicating whether the DSS is exhausted. */ | ||
| 34 | static atomic_b_t dss_exhausted; | ||
| 35 | /* Atomic current upper limit on DSS addresses. */ | ||
| 36 | static atomic_p_t dss_max; | ||
| 37 | |||
| 38 | /******************************************************************************/ | ||
| 39 | |||
| 40 | static void * | ||
| 41 | extent_dss_sbrk(intptr_t increment) { | ||
| 42 | #ifdef JEMALLOC_DSS | ||
| 43 | return sbrk(increment); | ||
| 44 | #else | ||
| 45 | not_implemented(); | ||
| 46 | return NULL; | ||
| 47 | #endif | ||
| 48 | } | ||
| 49 | |||
| 50 | dss_prec_t | ||
| 51 | extent_dss_prec_get(void) { | ||
| 52 | dss_prec_t ret; | ||
| 53 | |||
| 54 | if (!have_dss) { | ||
| 55 | return dss_prec_disabled; | ||
| 56 | } | ||
| 57 | ret = (dss_prec_t)atomic_load_u(&dss_prec_default, ATOMIC_ACQUIRE); | ||
| 58 | return ret; | ||
| 59 | } | ||
| 60 | |||
| 61 | bool | ||
| 62 | extent_dss_prec_set(dss_prec_t dss_prec) { | ||
| 63 | if (!have_dss) { | ||
| 64 | return (dss_prec != dss_prec_disabled); | ||
| 65 | } | ||
| 66 | atomic_store_u(&dss_prec_default, (unsigned)dss_prec, ATOMIC_RELEASE); | ||
| 67 | return false; | ||
| 68 | } | ||
| 69 | |||
| 70 | static void | ||
| 71 | extent_dss_extending_start(void) { | ||
| 72 | spin_t spinner = SPIN_INITIALIZER; | ||
| 73 | while (true) { | ||
| 74 | bool expected = false; | ||
| 75 | if (atomic_compare_exchange_weak_b(&dss_extending, &expected, | ||
| 76 | true, ATOMIC_ACQ_REL, ATOMIC_RELAXED)) { | ||
| 77 | break; | ||
| 78 | } | ||
| 79 | spin_adaptive(&spinner); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | static void | ||
| 84 | extent_dss_extending_finish(void) { | ||
| 85 | assert(atomic_load_b(&dss_extending, ATOMIC_RELAXED)); | ||
| 86 | |||
| 87 | atomic_store_b(&dss_extending, false, ATOMIC_RELEASE); | ||
| 88 | } | ||
| 89 | |||
| 90 | static void * | ||
| 91 | extent_dss_max_update(void *new_addr) { | ||
| 92 | /* | ||
| 93 | * Get the current end of the DSS as max_cur and assure that dss_max is | ||
| 94 | * up to date. | ||
| 95 | */ | ||
| 96 | void *max_cur = extent_dss_sbrk(0); | ||
| 97 | if (max_cur == (void *)-1) { | ||
| 98 | return NULL; | ||
| 99 | } | ||
| 100 | atomic_store_p(&dss_max, max_cur, ATOMIC_RELEASE); | ||
| 101 | /* Fixed new_addr can only be supported if it is at the edge of DSS. */ | ||
| 102 | if (new_addr != NULL && max_cur != new_addr) { | ||
| 103 | return NULL; | ||
| 104 | } | ||
| 105 | return max_cur; | ||
| 106 | } | ||
| 107 | |||
| 108 | void * | ||
| 109 | extent_alloc_dss(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size, | ||
| 110 | size_t alignment, bool *zero, bool *commit) { | ||
| 111 | edata_t *gap; | ||
| 112 | |||
| 113 | cassert(have_dss); | ||
| 114 | assert(size > 0); | ||
| 115 | assert(alignment == ALIGNMENT_CEILING(alignment, PAGE)); | ||
| 116 | |||
| 117 | /* | ||
| 118 | * sbrk() uses a signed increment argument, so take care not to | ||
| 119 | * interpret a large allocation request as a negative increment. | ||
| 120 | */ | ||
| 121 | if ((intptr_t)size < 0) { | ||
| 122 | return NULL; | ||
| 123 | } | ||
| 124 | |||
| 125 | gap = edata_cache_get(tsdn, &arena->pa_shard.edata_cache); | ||
| 126 | if (gap == NULL) { | ||
| 127 | return NULL; | ||
| 128 | } | ||
| 129 | |||
| 130 | extent_dss_extending_start(); | ||
| 131 | if (!atomic_load_b(&dss_exhausted, ATOMIC_ACQUIRE)) { | ||
| 132 | /* | ||
| 133 | * The loop is necessary to recover from races with other | ||
| 134 | * threads that are using the DSS for something other than | ||
| 135 | * malloc. | ||
| 136 | */ | ||
| 137 | while (true) { | ||
| 138 | void *max_cur = extent_dss_max_update(new_addr); | ||
| 139 | if (max_cur == NULL) { | ||
| 140 | goto label_oom; | ||
| 141 | } | ||
| 142 | |||
| 143 | bool head_state = opt_retain ? EXTENT_IS_HEAD : | ||
| 144 | EXTENT_NOT_HEAD; | ||
| 145 | /* | ||
| 146 | * Compute how much page-aligned gap space (if any) is | ||
| 147 | * necessary to satisfy alignment. This space can be | ||
| 148 | * recycled for later use. | ||
| 149 | */ | ||
| 150 | void *gap_addr_page = (void *)(PAGE_CEILING( | ||
| 151 | (uintptr_t)max_cur)); | ||
| 152 | void *ret = (void *)ALIGNMENT_CEILING( | ||
| 153 | (uintptr_t)gap_addr_page, alignment); | ||
| 154 | size_t gap_size_page = (uintptr_t)ret - | ||
| 155 | (uintptr_t)gap_addr_page; | ||
| 156 | if (gap_size_page != 0) { | ||
| 157 | edata_init(gap, arena_ind_get(arena), | ||
| 158 | gap_addr_page, gap_size_page, false, | ||
| 159 | SC_NSIZES, extent_sn_next( | ||
| 160 | &arena->pa_shard.pac), | ||
| 161 | extent_state_active, false, true, | ||
| 162 | EXTENT_PAI_PAC, head_state); | ||
| 163 | } | ||
| 164 | /* | ||
| 165 | * Compute the address just past the end of the desired | ||
| 166 | * allocation space. | ||
| 167 | */ | ||
| 168 | void *dss_next = (void *)((uintptr_t)ret + size); | ||
| 169 | if ((uintptr_t)ret < (uintptr_t)max_cur || | ||
| 170 | (uintptr_t)dss_next < (uintptr_t)max_cur) { | ||
| 171 | goto label_oom; /* Wrap-around. */ | ||
| 172 | } | ||
| 173 | /* Compute the increment, including subpage bytes. */ | ||
| 174 | void *gap_addr_subpage = max_cur; | ||
| 175 | size_t gap_size_subpage = (uintptr_t)ret - | ||
| 176 | (uintptr_t)gap_addr_subpage; | ||
| 177 | intptr_t incr = gap_size_subpage + size; | ||
| 178 | |||
| 179 | assert((uintptr_t)max_cur + incr == (uintptr_t)ret + | ||
| 180 | size); | ||
| 181 | |||
| 182 | /* Try to allocate. */ | ||
| 183 | void *dss_prev = extent_dss_sbrk(incr); | ||
| 184 | if (dss_prev == max_cur) { | ||
| 185 | /* Success. */ | ||
| 186 | atomic_store_p(&dss_max, dss_next, | ||
| 187 | ATOMIC_RELEASE); | ||
| 188 | extent_dss_extending_finish(); | ||
| 189 | |||
| 190 | if (gap_size_page != 0) { | ||
| 191 | ehooks_t *ehooks = arena_get_ehooks( | ||
| 192 | arena); | ||
| 193 | extent_dalloc_gap(tsdn, | ||
| 194 | &arena->pa_shard.pac, ehooks, gap); | ||
| 195 | } else { | ||
| 196 | edata_cache_put(tsdn, | ||
| 197 | &arena->pa_shard.edata_cache, gap); | ||
| 198 | } | ||
| 199 | if (!*commit) { | ||
| 200 | *commit = pages_decommit(ret, size); | ||
| 201 | } | ||
| 202 | if (*zero && *commit) { | ||
| 203 | edata_t edata = {0}; | ||
| 204 | ehooks_t *ehooks = arena_get_ehooks( | ||
| 205 | arena); | ||
| 206 | |||
| 207 | edata_init(&edata, | ||
| 208 | arena_ind_get(arena), ret, size, | ||
| 209 | size, false, SC_NSIZES, | ||
| 210 | extent_state_active, false, true, | ||
| 211 | EXTENT_PAI_PAC, head_state); | ||
| 212 | if (extent_purge_forced_wrapper(tsdn, | ||
| 213 | ehooks, &edata, 0, size)) { | ||
| 214 | memset(ret, 0, size); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | return ret; | ||
| 218 | } | ||
| 219 | /* | ||
| 220 | * Failure, whether due to OOM or a race with a raw | ||
| 221 | * sbrk() call from outside the allocator. | ||
| 222 | */ | ||
| 223 | if (dss_prev == (void *)-1) { | ||
| 224 | /* OOM. */ | ||
| 225 | atomic_store_b(&dss_exhausted, true, | ||
| 226 | ATOMIC_RELEASE); | ||
| 227 | goto label_oom; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | } | ||
| 231 | label_oom: | ||
| 232 | extent_dss_extending_finish(); | ||
| 233 | edata_cache_put(tsdn, &arena->pa_shard.edata_cache, gap); | ||
| 234 | return NULL; | ||
| 235 | } | ||
| 236 | |||
| 237 | static bool | ||
| 238 | extent_in_dss_helper(void *addr, void *max) { | ||
| 239 | return ((uintptr_t)addr >= (uintptr_t)dss_base && (uintptr_t)addr < | ||
| 240 | (uintptr_t)max); | ||
| 241 | } | ||
| 242 | |||
| 243 | bool | ||
| 244 | extent_in_dss(void *addr) { | ||
| 245 | cassert(have_dss); | ||
| 246 | |||
| 247 | return extent_in_dss_helper(addr, atomic_load_p(&dss_max, | ||
| 248 | ATOMIC_ACQUIRE)); | ||
| 249 | } | ||
| 250 | |||
| 251 | bool | ||
| 252 | extent_dss_mergeable(void *addr_a, void *addr_b) { | ||
| 253 | void *max; | ||
| 254 | |||
| 255 | cassert(have_dss); | ||
| 256 | |||
| 257 | if ((uintptr_t)addr_a < (uintptr_t)dss_base && (uintptr_t)addr_b < | ||
| 258 | (uintptr_t)dss_base) { | ||
| 259 | return true; | ||
| 260 | } | ||
| 261 | |||
| 262 | max = atomic_load_p(&dss_max, ATOMIC_ACQUIRE); | ||
| 263 | return (extent_in_dss_helper(addr_a, max) == | ||
| 264 | extent_in_dss_helper(addr_b, max)); | ||
| 265 | } | ||
| 266 | |||
| 267 | void | ||
| 268 | extent_dss_boot(void) { | ||
| 269 | cassert(have_dss); | ||
| 270 | |||
| 271 | dss_base = extent_dss_sbrk(0); | ||
| 272 | atomic_store_b(&dss_extending, false, ATOMIC_RELAXED); | ||
| 273 | atomic_store_b(&dss_exhausted, dss_base == (void *)-1, ATOMIC_RELAXED); | ||
| 274 | atomic_store_p(&dss_max, dss_base, ATOMIC_RELAXED); | ||
| 275 | } | ||
| 276 | |||
| 277 | /******************************************************************************/ | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/extent_mmap.c b/examples/redis-unstable/deps/jemalloc/src/extent_mmap.c deleted file mode 100644 index 5f0ee2d..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/extent_mmap.c +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/extent_mmap.h" | ||
| 6 | |||
| 7 | /******************************************************************************/ | ||
| 8 | /* Data. */ | ||
| 9 | |||
| 10 | bool opt_retain = | ||
| 11 | #ifdef JEMALLOC_RETAIN | ||
| 12 | true | ||
| 13 | #else | ||
| 14 | false | ||
| 15 | #endif | ||
| 16 | ; | ||
| 17 | |||
| 18 | /******************************************************************************/ | ||
| 19 | |||
| 20 | void * | ||
| 21 | extent_alloc_mmap(void *new_addr, size_t size, size_t alignment, bool *zero, | ||
| 22 | bool *commit) { | ||
| 23 | assert(alignment == ALIGNMENT_CEILING(alignment, PAGE)); | ||
| 24 | void *ret = pages_map(new_addr, size, alignment, commit); | ||
| 25 | if (ret == NULL) { | ||
| 26 | return NULL; | ||
| 27 | } | ||
| 28 | assert(ret != NULL); | ||
| 29 | if (*commit) { | ||
| 30 | *zero = true; | ||
| 31 | } | ||
| 32 | return ret; | ||
| 33 | } | ||
| 34 | |||
| 35 | bool | ||
| 36 | extent_dalloc_mmap(void *addr, size_t size) { | ||
| 37 | if (!opt_retain) { | ||
| 38 | pages_unmap(addr, size); | ||
| 39 | } | ||
| 40 | return opt_retain; | ||
| 41 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/fxp.c b/examples/redis-unstable/deps/jemalloc/src/fxp.c deleted file mode 100644 index 96585f0..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/fxp.c +++ /dev/null | |||
| @@ -1,124 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/fxp.h" | ||
| 5 | |||
| 6 | static bool | ||
| 7 | fxp_isdigit(char c) { | ||
| 8 | return '0' <= c && c <= '9'; | ||
| 9 | } | ||
| 10 | |||
| 11 | bool | ||
| 12 | fxp_parse(fxp_t *result, const char *str, char **end) { | ||
| 13 | /* | ||
| 14 | * Using malloc_strtoumax in this method isn't as handy as you might | ||
| 15 | * expect (I tried). In the fractional part, significant leading zeros | ||
| 16 | * mean that you still need to do your own parsing, now with trickier | ||
| 17 | * math. In the integer part, the casting (uintmax_t to uint32_t) | ||
| 18 | * forces more reasoning about bounds than just checking for overflow as | ||
| 19 | * we parse. | ||
| 20 | */ | ||
| 21 | uint32_t integer_part = 0; | ||
| 22 | |||
| 23 | const char *cur = str; | ||
| 24 | |||
| 25 | /* The string must start with a digit or a decimal point. */ | ||
| 26 | if (*cur != '.' && !fxp_isdigit(*cur)) { | ||
| 27 | return true; | ||
| 28 | } | ||
| 29 | |||
| 30 | while ('0' <= *cur && *cur <= '9') { | ||
| 31 | integer_part *= 10; | ||
| 32 | integer_part += *cur - '0'; | ||
| 33 | if (integer_part >= (1U << 16)) { | ||
| 34 | return true; | ||
| 35 | } | ||
| 36 | cur++; | ||
| 37 | } | ||
| 38 | |||
| 39 | /* | ||
| 40 | * We've parsed all digits at the beginning of the string, without | ||
| 41 | * overflow. Either we're done, or there's a fractional part. | ||
| 42 | */ | ||
| 43 | if (*cur != '.') { | ||
| 44 | *result = (integer_part << 16); | ||
| 45 | if (end != NULL) { | ||
| 46 | *end = (char *)cur; | ||
| 47 | } | ||
| 48 | return false; | ||
| 49 | } | ||
| 50 | |||
| 51 | /* There's a fractional part. */ | ||
| 52 | cur++; | ||
| 53 | if (!fxp_isdigit(*cur)) { | ||
| 54 | /* Shouldn't end on the decimal point. */ | ||
| 55 | return true; | ||
| 56 | } | ||
| 57 | |||
| 58 | /* | ||
| 59 | * We use a lot of precision for the fractional part, even though we'll | ||
| 60 | * discard most of it; this lets us get exact values for the important | ||
| 61 | * special case where the denominator is a small power of 2 (for | ||
| 62 | * instance, 1/512 == 0.001953125 is exactly representable even with | ||
| 63 | * only 16 bits of fractional precision). We need to left-shift by 16 | ||
| 64 | * before dividing so we pick the number of digits to be | ||
| 65 | * floor(log(2**48)) = 14. | ||
| 66 | */ | ||
| 67 | uint64_t fractional_part = 0; | ||
| 68 | uint64_t frac_div = 1; | ||
| 69 | for (int i = 0; i < FXP_FRACTIONAL_PART_DIGITS; i++) { | ||
| 70 | fractional_part *= 10; | ||
| 71 | frac_div *= 10; | ||
| 72 | if (fxp_isdigit(*cur)) { | ||
| 73 | fractional_part += *cur - '0'; | ||
| 74 | cur++; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | /* | ||
| 78 | * We only parse the first maxdigits characters, but we can still ignore | ||
| 79 | * any digits after that. | ||
| 80 | */ | ||
| 81 | while (fxp_isdigit(*cur)) { | ||
| 82 | cur++; | ||
| 83 | } | ||
| 84 | |||
| 85 | assert(fractional_part < frac_div); | ||
| 86 | uint32_t fractional_repr = (uint32_t)( | ||
| 87 | (fractional_part << 16) / frac_div); | ||
| 88 | |||
| 89 | /* Success! */ | ||
| 90 | *result = (integer_part << 16) + fractional_repr; | ||
| 91 | if (end != NULL) { | ||
| 92 | *end = (char *)cur; | ||
| 93 | } | ||
| 94 | return false; | ||
| 95 | } | ||
| 96 | |||
| 97 | void | ||
| 98 | fxp_print(fxp_t a, char buf[FXP_BUF_SIZE]) { | ||
| 99 | uint32_t integer_part = fxp_round_down(a); | ||
| 100 | uint32_t fractional_part = (a & ((1U << 16) - 1)); | ||
| 101 | |||
| 102 | int leading_fraction_zeros = 0; | ||
| 103 | uint64_t fraction_digits = fractional_part; | ||
| 104 | for (int i = 0; i < FXP_FRACTIONAL_PART_DIGITS; i++) { | ||
| 105 | if (fraction_digits < (1U << 16) | ||
| 106 | && fraction_digits * 10 >= (1U << 16)) { | ||
| 107 | leading_fraction_zeros = i; | ||
| 108 | } | ||
| 109 | fraction_digits *= 10; | ||
| 110 | } | ||
| 111 | fraction_digits >>= 16; | ||
| 112 | while (fraction_digits > 0 && fraction_digits % 10 == 0) { | ||
| 113 | fraction_digits /= 10; | ||
| 114 | } | ||
| 115 | |||
| 116 | size_t printed = malloc_snprintf(buf, FXP_BUF_SIZE, "%"FMTu32".", | ||
| 117 | integer_part); | ||
| 118 | for (int i = 0; i < leading_fraction_zeros; i++) { | ||
| 119 | buf[printed] = '0'; | ||
| 120 | printed++; | ||
| 121 | } | ||
| 122 | malloc_snprintf(&buf[printed], FXP_BUF_SIZE - printed, "%"FMTu64, | ||
| 123 | fraction_digits); | ||
| 124 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/hook.c b/examples/redis-unstable/deps/jemalloc/src/hook.c deleted file mode 100644 index 493edbb..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/hook.c +++ /dev/null | |||
| @@ -1,195 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | |||
| 3 | #include "jemalloc/internal/hook.h" | ||
| 4 | |||
| 5 | #include "jemalloc/internal/atomic.h" | ||
| 6 | #include "jemalloc/internal/mutex.h" | ||
| 7 | #include "jemalloc/internal/seq.h" | ||
| 8 | |||
| 9 | typedef struct hooks_internal_s hooks_internal_t; | ||
| 10 | struct hooks_internal_s { | ||
| 11 | hooks_t hooks; | ||
| 12 | bool in_use; | ||
| 13 | }; | ||
| 14 | |||
| 15 | seq_define(hooks_internal_t, hooks) | ||
| 16 | |||
| 17 | static atomic_u_t nhooks = ATOMIC_INIT(0); | ||
| 18 | static seq_hooks_t hooks[HOOK_MAX]; | ||
| 19 | static malloc_mutex_t hooks_mu; | ||
| 20 | |||
| 21 | bool | ||
| 22 | hook_boot() { | ||
| 23 | return malloc_mutex_init(&hooks_mu, "hooks", WITNESS_RANK_HOOK, | ||
| 24 | malloc_mutex_rank_exclusive); | ||
| 25 | } | ||
| 26 | |||
| 27 | static void * | ||
| 28 | hook_install_locked(hooks_t *to_install) { | ||
| 29 | hooks_internal_t hooks_internal; | ||
| 30 | for (int i = 0; i < HOOK_MAX; i++) { | ||
| 31 | bool success = seq_try_load_hooks(&hooks_internal, &hooks[i]); | ||
| 32 | /* We hold mu; no concurrent access. */ | ||
| 33 | assert(success); | ||
| 34 | if (!hooks_internal.in_use) { | ||
| 35 | hooks_internal.hooks = *to_install; | ||
| 36 | hooks_internal.in_use = true; | ||
| 37 | seq_store_hooks(&hooks[i], &hooks_internal); | ||
| 38 | atomic_store_u(&nhooks, | ||
| 39 | atomic_load_u(&nhooks, ATOMIC_RELAXED) + 1, | ||
| 40 | ATOMIC_RELAXED); | ||
| 41 | return &hooks[i]; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | return NULL; | ||
| 45 | } | ||
| 46 | |||
| 47 | void * | ||
| 48 | hook_install(tsdn_t *tsdn, hooks_t *to_install) { | ||
| 49 | malloc_mutex_lock(tsdn, &hooks_mu); | ||
| 50 | void *ret = hook_install_locked(to_install); | ||
| 51 | if (ret != NULL) { | ||
| 52 | tsd_global_slow_inc(tsdn); | ||
| 53 | } | ||
| 54 | malloc_mutex_unlock(tsdn, &hooks_mu); | ||
| 55 | return ret; | ||
| 56 | } | ||
| 57 | |||
| 58 | static void | ||
| 59 | hook_remove_locked(seq_hooks_t *to_remove) { | ||
| 60 | hooks_internal_t hooks_internal; | ||
| 61 | bool success = seq_try_load_hooks(&hooks_internal, to_remove); | ||
| 62 | /* We hold mu; no concurrent access. */ | ||
| 63 | assert(success); | ||
| 64 | /* Should only remove hooks that were added. */ | ||
| 65 | assert(hooks_internal.in_use); | ||
| 66 | hooks_internal.in_use = false; | ||
| 67 | seq_store_hooks(to_remove, &hooks_internal); | ||
| 68 | atomic_store_u(&nhooks, atomic_load_u(&nhooks, ATOMIC_RELAXED) - 1, | ||
| 69 | ATOMIC_RELAXED); | ||
| 70 | } | ||
| 71 | |||
| 72 | void | ||
| 73 | hook_remove(tsdn_t *tsdn, void *opaque) { | ||
| 74 | if (config_debug) { | ||
| 75 | char *hooks_begin = (char *)&hooks[0]; | ||
| 76 | char *hooks_end = (char *)&hooks[HOOK_MAX]; | ||
| 77 | char *hook = (char *)opaque; | ||
| 78 | assert(hooks_begin <= hook && hook < hooks_end | ||
| 79 | && (hook - hooks_begin) % sizeof(seq_hooks_t) == 0); | ||
| 80 | } | ||
| 81 | malloc_mutex_lock(tsdn, &hooks_mu); | ||
| 82 | hook_remove_locked((seq_hooks_t *)opaque); | ||
| 83 | tsd_global_slow_dec(tsdn); | ||
| 84 | malloc_mutex_unlock(tsdn, &hooks_mu); | ||
| 85 | } | ||
| 86 | |||
| 87 | #define FOR_EACH_HOOK_BEGIN(hooks_internal_ptr) \ | ||
| 88 | for (int for_each_hook_counter = 0; \ | ||
| 89 | for_each_hook_counter < HOOK_MAX; \ | ||
| 90 | for_each_hook_counter++) { \ | ||
| 91 | bool for_each_hook_success = seq_try_load_hooks( \ | ||
| 92 | (hooks_internal_ptr), &hooks[for_each_hook_counter]); \ | ||
| 93 | if (!for_each_hook_success) { \ | ||
| 94 | continue; \ | ||
| 95 | } \ | ||
| 96 | if (!(hooks_internal_ptr)->in_use) { \ | ||
| 97 | continue; \ | ||
| 98 | } | ||
| 99 | #define FOR_EACH_HOOK_END \ | ||
| 100 | } | ||
| 101 | |||
| 102 | static bool * | ||
| 103 | hook_reentrantp() { | ||
| 104 | /* | ||
| 105 | * We prevent user reentrancy within hooks. This is basically just a | ||
| 106 | * thread-local bool that triggers an early-exit. | ||
| 107 | * | ||
| 108 | * We don't fold in_hook into reentrancy. There are two reasons for | ||
| 109 | * this: | ||
| 110 | * - Right now, we turn on reentrancy during things like extent hook | ||
| 111 | * execution. Allocating during extent hooks is not officially | ||
| 112 | * supported, but we don't want to break it for the time being. These | ||
| 113 | * sorts of allocations should probably still be hooked, though. | ||
| 114 | * - If a hook allocates, we may want it to be relatively fast (after | ||
| 115 | * all, it executes on every allocator operation). Turning on | ||
| 116 | * reentrancy is a fairly heavyweight mode (disabling tcache, | ||
| 117 | * redirecting to arena 0, etc.). It's possible we may one day want | ||
| 118 | * to turn on reentrant mode here, if it proves too difficult to keep | ||
| 119 | * this working. But that's fairly easy for us to see; OTOH, people | ||
| 120 | * not using hooks because they're too slow is easy for us to miss. | ||
| 121 | * | ||
| 122 | * The tricky part is | ||
| 123 | * that this code might get invoked even if we don't have access to tsd. | ||
| 124 | * This function mimics getting a pointer to thread-local data, except | ||
| 125 | * that it might secretly return a pointer to some global data if we | ||
| 126 | * know that the caller will take the early-exit path. | ||
| 127 | * If we return a bool that indicates that we are reentrant, then the | ||
| 128 | * caller will go down the early exit path, leaving the global | ||
| 129 | * untouched. | ||
| 130 | */ | ||
| 131 | static bool in_hook_global = true; | ||
| 132 | tsdn_t *tsdn = tsdn_fetch(); | ||
| 133 | bool *in_hook = tsdn_in_hookp_get(tsdn); | ||
| 134 | if (in_hook!= NULL) { | ||
| 135 | return in_hook; | ||
| 136 | } | ||
| 137 | return &in_hook_global; | ||
| 138 | } | ||
| 139 | |||
| 140 | #define HOOK_PROLOGUE \ | ||
| 141 | if (likely(atomic_load_u(&nhooks, ATOMIC_RELAXED) == 0)) { \ | ||
| 142 | return; \ | ||
| 143 | } \ | ||
| 144 | bool *in_hook = hook_reentrantp(); \ | ||
| 145 | if (*in_hook) { \ | ||
| 146 | return; \ | ||
| 147 | } \ | ||
| 148 | *in_hook = true; | ||
| 149 | |||
| 150 | #define HOOK_EPILOGUE \ | ||
| 151 | *in_hook = false; | ||
| 152 | |||
| 153 | void | ||
| 154 | hook_invoke_alloc(hook_alloc_t type, void *result, uintptr_t result_raw, | ||
| 155 | uintptr_t args_raw[3]) { | ||
| 156 | HOOK_PROLOGUE | ||
| 157 | |||
| 158 | hooks_internal_t hook; | ||
| 159 | FOR_EACH_HOOK_BEGIN(&hook) | ||
| 160 | hook_alloc h = hook.hooks.alloc_hook; | ||
| 161 | if (h != NULL) { | ||
| 162 | h(hook.hooks.extra, type, result, result_raw, args_raw); | ||
| 163 | } | ||
| 164 | FOR_EACH_HOOK_END | ||
| 165 | |||
| 166 | HOOK_EPILOGUE | ||
| 167 | } | ||
| 168 | |||
| 169 | void | ||
| 170 | hook_invoke_dalloc(hook_dalloc_t type, void *address, uintptr_t args_raw[3]) { | ||
| 171 | HOOK_PROLOGUE | ||
| 172 | hooks_internal_t hook; | ||
| 173 | FOR_EACH_HOOK_BEGIN(&hook) | ||
| 174 | hook_dalloc h = hook.hooks.dalloc_hook; | ||
| 175 | if (h != NULL) { | ||
| 176 | h(hook.hooks.extra, type, address, args_raw); | ||
| 177 | } | ||
| 178 | FOR_EACH_HOOK_END | ||
| 179 | HOOK_EPILOGUE | ||
| 180 | } | ||
| 181 | |||
| 182 | void | ||
| 183 | hook_invoke_expand(hook_expand_t type, void *address, size_t old_usize, | ||
| 184 | size_t new_usize, uintptr_t result_raw, uintptr_t args_raw[4]) { | ||
| 185 | HOOK_PROLOGUE | ||
| 186 | hooks_internal_t hook; | ||
| 187 | FOR_EACH_HOOK_BEGIN(&hook) | ||
| 188 | hook_expand h = hook.hooks.expand_hook; | ||
| 189 | if (h != NULL) { | ||
| 190 | h(hook.hooks.extra, type, address, old_usize, new_usize, | ||
| 191 | result_raw, args_raw); | ||
| 192 | } | ||
| 193 | FOR_EACH_HOOK_END | ||
| 194 | HOOK_EPILOGUE | ||
| 195 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/hpa.c b/examples/redis-unstable/deps/jemalloc/src/hpa.c deleted file mode 100644 index 7e2aeba..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/hpa.c +++ /dev/null | |||
| @@ -1,1044 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/hpa.h" | ||
| 5 | |||
| 6 | #include "jemalloc/internal/fb.h" | ||
| 7 | #include "jemalloc/internal/witness.h" | ||
| 8 | |||
| 9 | #define HPA_EDEN_SIZE (128 * HUGEPAGE) | ||
| 10 | |||
| 11 | static edata_t *hpa_alloc(tsdn_t *tsdn, pai_t *self, size_t size, | ||
| 12 | size_t alignment, bool zero, bool guarded, bool frequent_reuse, | ||
| 13 | bool *deferred_work_generated); | ||
| 14 | static size_t hpa_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size, | ||
| 15 | size_t nallocs, edata_list_active_t *results, bool *deferred_work_generated); | ||
| 16 | static bool hpa_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 17 | size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); | ||
| 18 | static bool hpa_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 19 | size_t old_size, size_t new_size, bool *deferred_work_generated); | ||
| 20 | static void hpa_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 21 | bool *deferred_work_generated); | ||
| 22 | static void hpa_dalloc_batch(tsdn_t *tsdn, pai_t *self, | ||
| 23 | edata_list_active_t *list, bool *deferred_work_generated); | ||
| 24 | static uint64_t hpa_time_until_deferred_work(tsdn_t *tsdn, pai_t *self); | ||
| 25 | |||
| 26 | bool | ||
| 27 | hpa_supported() { | ||
| 28 | #ifdef _WIN32 | ||
| 29 | /* | ||
| 30 | * At least until the API and implementation is somewhat settled, we | ||
| 31 | * don't want to try to debug the VM subsystem on the hardest-to-test | ||
| 32 | * platform. | ||
| 33 | */ | ||
| 34 | return false; | ||
| 35 | #endif | ||
| 36 | if (!pages_can_hugify) { | ||
| 37 | return false; | ||
| 38 | } | ||
| 39 | /* | ||
| 40 | * We fundamentally rely on a address-space-hungry growth strategy for | ||
| 41 | * hugepages. | ||
| 42 | */ | ||
| 43 | if (LG_SIZEOF_PTR != 3) { | ||
| 44 | return false; | ||
| 45 | } | ||
| 46 | /* | ||
| 47 | * If we couldn't detect the value of HUGEPAGE, HUGEPAGE_PAGES becomes | ||
| 48 | * this sentinel value -- see the comment in pages.h. | ||
| 49 | */ | ||
| 50 | if (HUGEPAGE_PAGES == 1) { | ||
| 51 | return false; | ||
| 52 | } | ||
| 53 | return true; | ||
| 54 | } | ||
| 55 | |||
| 56 | static void | ||
| 57 | hpa_do_consistency_checks(hpa_shard_t *shard) { | ||
| 58 | assert(shard->base != NULL); | ||
| 59 | } | ||
| 60 | |||
| 61 | bool | ||
| 62 | hpa_central_init(hpa_central_t *central, base_t *base, const hpa_hooks_t *hooks) { | ||
| 63 | /* malloc_conf processing should have filtered out these cases. */ | ||
| 64 | assert(hpa_supported()); | ||
| 65 | bool err; | ||
| 66 | err = malloc_mutex_init(¢ral->grow_mtx, "hpa_central_grow", | ||
| 67 | WITNESS_RANK_HPA_CENTRAL_GROW, malloc_mutex_rank_exclusive); | ||
| 68 | if (err) { | ||
| 69 | return true; | ||
| 70 | } | ||
| 71 | err = malloc_mutex_init(¢ral->mtx, "hpa_central", | ||
| 72 | WITNESS_RANK_HPA_CENTRAL, malloc_mutex_rank_exclusive); | ||
| 73 | if (err) { | ||
| 74 | return true; | ||
| 75 | } | ||
| 76 | central->base = base; | ||
| 77 | central->eden = NULL; | ||
| 78 | central->eden_len = 0; | ||
| 79 | central->age_counter = 0; | ||
| 80 | central->hooks = *hooks; | ||
| 81 | return false; | ||
| 82 | } | ||
| 83 | |||
| 84 | static hpdata_t * | ||
| 85 | hpa_alloc_ps(tsdn_t *tsdn, hpa_central_t *central) { | ||
| 86 | return (hpdata_t *)base_alloc(tsdn, central->base, sizeof(hpdata_t), | ||
| 87 | CACHELINE); | ||
| 88 | } | ||
| 89 | |||
| 90 | hpdata_t * | ||
| 91 | hpa_central_extract(tsdn_t *tsdn, hpa_central_t *central, size_t size, | ||
| 92 | bool *oom) { | ||
| 93 | /* Don't yet support big allocations; these should get filtered out. */ | ||
| 94 | assert(size <= HUGEPAGE); | ||
| 95 | /* | ||
| 96 | * Should only try to extract from the central allocator if the local | ||
| 97 | * shard is exhausted. We should hold the grow_mtx on that shard. | ||
| 98 | */ | ||
| 99 | witness_assert_positive_depth_to_rank( | ||
| 100 | tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_HPA_SHARD_GROW); | ||
| 101 | |||
| 102 | malloc_mutex_lock(tsdn, ¢ral->grow_mtx); | ||
| 103 | *oom = false; | ||
| 104 | |||
| 105 | hpdata_t *ps = NULL; | ||
| 106 | |||
| 107 | /* Is eden a perfect fit? */ | ||
| 108 | if (central->eden != NULL && central->eden_len == HUGEPAGE) { | ||
| 109 | ps = hpa_alloc_ps(tsdn, central); | ||
| 110 | if (ps == NULL) { | ||
| 111 | *oom = true; | ||
| 112 | malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); | ||
| 113 | return NULL; | ||
| 114 | } | ||
| 115 | hpdata_init(ps, central->eden, central->age_counter++); | ||
| 116 | central->eden = NULL; | ||
| 117 | central->eden_len = 0; | ||
| 118 | malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); | ||
| 119 | return ps; | ||
| 120 | } | ||
| 121 | |||
| 122 | /* | ||
| 123 | * We're about to try to allocate from eden by splitting. If eden is | ||
| 124 | * NULL, we have to allocate it too. Otherwise, we just have to | ||
| 125 | * allocate an edata_t for the new psset. | ||
| 126 | */ | ||
| 127 | if (central->eden == NULL) { | ||
| 128 | /* | ||
| 129 | * During development, we're primarily concerned with systems | ||
| 130 | * with overcommit. Eventually, we should be more careful here. | ||
| 131 | */ | ||
| 132 | bool commit = true; | ||
| 133 | /* Allocate address space, bailing if we fail. */ | ||
| 134 | void *new_eden = pages_map(NULL, HPA_EDEN_SIZE, HUGEPAGE, | ||
| 135 | &commit); | ||
| 136 | if (new_eden == NULL) { | ||
| 137 | *oom = true; | ||
| 138 | malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); | ||
| 139 | return NULL; | ||
| 140 | } | ||
| 141 | ps = hpa_alloc_ps(tsdn, central); | ||
| 142 | if (ps == NULL) { | ||
| 143 | pages_unmap(new_eden, HPA_EDEN_SIZE); | ||
| 144 | *oom = true; | ||
| 145 | malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); | ||
| 146 | return NULL; | ||
| 147 | } | ||
| 148 | central->eden = new_eden; | ||
| 149 | central->eden_len = HPA_EDEN_SIZE; | ||
| 150 | } else { | ||
| 151 | /* Eden is already nonempty; only need an edata for ps. */ | ||
| 152 | ps = hpa_alloc_ps(tsdn, central); | ||
| 153 | if (ps == NULL) { | ||
| 154 | *oom = true; | ||
| 155 | malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); | ||
| 156 | return NULL; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | assert(ps != NULL); | ||
| 160 | assert(central->eden != NULL); | ||
| 161 | assert(central->eden_len > HUGEPAGE); | ||
| 162 | assert(central->eden_len % HUGEPAGE == 0); | ||
| 163 | assert(HUGEPAGE_ADDR2BASE(central->eden) == central->eden); | ||
| 164 | |||
| 165 | hpdata_init(ps, central->eden, central->age_counter++); | ||
| 166 | |||
| 167 | char *eden_char = (char *)central->eden; | ||
| 168 | eden_char += HUGEPAGE; | ||
| 169 | central->eden = (void *)eden_char; | ||
| 170 | central->eden_len -= HUGEPAGE; | ||
| 171 | |||
| 172 | malloc_mutex_unlock(tsdn, ¢ral->grow_mtx); | ||
| 173 | |||
| 174 | return ps; | ||
| 175 | } | ||
| 176 | |||
| 177 | bool | ||
| 178 | hpa_shard_init(hpa_shard_t *shard, hpa_central_t *central, emap_t *emap, | ||
| 179 | base_t *base, edata_cache_t *edata_cache, unsigned ind, | ||
| 180 | const hpa_shard_opts_t *opts) { | ||
| 181 | /* malloc_conf processing should have filtered out these cases. */ | ||
| 182 | assert(hpa_supported()); | ||
| 183 | bool err; | ||
| 184 | err = malloc_mutex_init(&shard->grow_mtx, "hpa_shard_grow", | ||
| 185 | WITNESS_RANK_HPA_SHARD_GROW, malloc_mutex_rank_exclusive); | ||
| 186 | if (err) { | ||
| 187 | return true; | ||
| 188 | } | ||
| 189 | err = malloc_mutex_init(&shard->mtx, "hpa_shard", | ||
| 190 | WITNESS_RANK_HPA_SHARD, malloc_mutex_rank_exclusive); | ||
| 191 | if (err) { | ||
| 192 | return true; | ||
| 193 | } | ||
| 194 | |||
| 195 | assert(edata_cache != NULL); | ||
| 196 | shard->central = central; | ||
| 197 | shard->base = base; | ||
| 198 | edata_cache_fast_init(&shard->ecf, edata_cache); | ||
| 199 | psset_init(&shard->psset); | ||
| 200 | shard->age_counter = 0; | ||
| 201 | shard->ind = ind; | ||
| 202 | shard->emap = emap; | ||
| 203 | |||
| 204 | shard->opts = *opts; | ||
| 205 | |||
| 206 | shard->npending_purge = 0; | ||
| 207 | nstime_init_zero(&shard->last_purge); | ||
| 208 | |||
| 209 | shard->stats.npurge_passes = 0; | ||
| 210 | shard->stats.npurges = 0; | ||
| 211 | shard->stats.nhugifies = 0; | ||
| 212 | shard->stats.ndehugifies = 0; | ||
| 213 | |||
| 214 | /* | ||
| 215 | * Fill these in last, so that if an hpa_shard gets used despite | ||
| 216 | * initialization failing, we'll at least crash instead of just | ||
| 217 | * operating on corrupted data. | ||
| 218 | */ | ||
| 219 | shard->pai.alloc = &hpa_alloc; | ||
| 220 | shard->pai.alloc_batch = &hpa_alloc_batch; | ||
| 221 | shard->pai.expand = &hpa_expand; | ||
| 222 | shard->pai.shrink = &hpa_shrink; | ||
| 223 | shard->pai.dalloc = &hpa_dalloc; | ||
| 224 | shard->pai.dalloc_batch = &hpa_dalloc_batch; | ||
| 225 | shard->pai.time_until_deferred_work = &hpa_time_until_deferred_work; | ||
| 226 | |||
| 227 | hpa_do_consistency_checks(shard); | ||
| 228 | |||
| 229 | return false; | ||
| 230 | } | ||
| 231 | |||
| 232 | /* | ||
| 233 | * Note that the stats functions here follow the usual stats naming conventions; | ||
| 234 | * "merge" obtains the stats from some live object of instance, while "accum" | ||
| 235 | * only combines the stats from one stats objet to another. Hence the lack of | ||
| 236 | * locking here. | ||
| 237 | */ | ||
| 238 | static void | ||
| 239 | hpa_shard_nonderived_stats_accum(hpa_shard_nonderived_stats_t *dst, | ||
| 240 | hpa_shard_nonderived_stats_t *src) { | ||
| 241 | dst->npurge_passes += src->npurge_passes; | ||
| 242 | dst->npurges += src->npurges; | ||
| 243 | dst->nhugifies += src->nhugifies; | ||
| 244 | dst->ndehugifies += src->ndehugifies; | ||
| 245 | } | ||
| 246 | |||
| 247 | void | ||
| 248 | hpa_shard_stats_accum(hpa_shard_stats_t *dst, hpa_shard_stats_t *src) { | ||
| 249 | psset_stats_accum(&dst->psset_stats, &src->psset_stats); | ||
| 250 | hpa_shard_nonderived_stats_accum(&dst->nonderived_stats, | ||
| 251 | &src->nonderived_stats); | ||
| 252 | } | ||
| 253 | |||
| 254 | void | ||
| 255 | hpa_shard_stats_merge(tsdn_t *tsdn, hpa_shard_t *shard, | ||
| 256 | hpa_shard_stats_t *dst) { | ||
| 257 | hpa_do_consistency_checks(shard); | ||
| 258 | |||
| 259 | malloc_mutex_lock(tsdn, &shard->grow_mtx); | ||
| 260 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 261 | psset_stats_accum(&dst->psset_stats, &shard->psset.stats); | ||
| 262 | hpa_shard_nonderived_stats_accum(&dst->nonderived_stats, &shard->stats); | ||
| 263 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 264 | malloc_mutex_unlock(tsdn, &shard->grow_mtx); | ||
| 265 | } | ||
| 266 | |||
| 267 | static bool | ||
| 268 | hpa_good_hugification_candidate(hpa_shard_t *shard, hpdata_t *ps) { | ||
| 269 | /* | ||
| 270 | * Note that this needs to be >= rather than just >, because of the | ||
| 271 | * important special case in which the hugification threshold is exactly | ||
| 272 | * HUGEPAGE. | ||
| 273 | */ | ||
| 274 | return hpdata_nactive_get(ps) * PAGE | ||
| 275 | >= shard->opts.hugification_threshold; | ||
| 276 | } | ||
| 277 | |||
| 278 | static size_t | ||
| 279 | hpa_adjusted_ndirty(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 280 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 281 | return psset_ndirty(&shard->psset) - shard->npending_purge; | ||
| 282 | } | ||
| 283 | |||
| 284 | static size_t | ||
| 285 | hpa_ndirty_max(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 286 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 287 | if (shard->opts.dirty_mult == (fxp_t)-1) { | ||
| 288 | return (size_t)-1; | ||
| 289 | } | ||
| 290 | return fxp_mul_frac(psset_nactive(&shard->psset), | ||
| 291 | shard->opts.dirty_mult); | ||
| 292 | } | ||
| 293 | |||
| 294 | static bool | ||
| 295 | hpa_hugify_blocked_by_ndirty(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 296 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 297 | hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); | ||
| 298 | if (to_hugify == NULL) { | ||
| 299 | return false; | ||
| 300 | } | ||
| 301 | return hpa_adjusted_ndirty(tsdn, shard) | ||
| 302 | + hpdata_nretained_get(to_hugify) > hpa_ndirty_max(tsdn, shard); | ||
| 303 | } | ||
| 304 | |||
| 305 | static bool | ||
| 306 | hpa_should_purge(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 307 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 308 | if (hpa_adjusted_ndirty(tsdn, shard) > hpa_ndirty_max(tsdn, shard)) { | ||
| 309 | return true; | ||
| 310 | } | ||
| 311 | if (hpa_hugify_blocked_by_ndirty(tsdn, shard)) { | ||
| 312 | return true; | ||
| 313 | } | ||
| 314 | return false; | ||
| 315 | } | ||
| 316 | |||
| 317 | static void | ||
| 318 | hpa_update_purge_hugify_eligibility(tsdn_t *tsdn, hpa_shard_t *shard, | ||
| 319 | hpdata_t *ps) { | ||
| 320 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 321 | if (hpdata_changing_state_get(ps)) { | ||
| 322 | hpdata_purge_allowed_set(ps, false); | ||
| 323 | hpdata_disallow_hugify(ps); | ||
| 324 | return; | ||
| 325 | } | ||
| 326 | /* | ||
| 327 | * Hugepages are distinctly costly to purge, so try to avoid it unless | ||
| 328 | * they're *particularly* full of dirty pages. Eventually, we should | ||
| 329 | * use a smarter / more dynamic heuristic for situations where we have | ||
| 330 | * to manually hugify. | ||
| 331 | * | ||
| 332 | * In situations where we don't manually hugify, this problem is | ||
| 333 | * reduced. The "bad" situation we're trying to avoid is one's that's | ||
| 334 | * common in some Linux configurations (where both enabled and defrag | ||
| 335 | * are set to madvise) that can lead to long latency spikes on the first | ||
| 336 | * access after a hugification. The ideal policy in such configurations | ||
| 337 | * is probably time-based for both purging and hugifying; only hugify a | ||
| 338 | * hugepage if it's met the criteria for some extended period of time, | ||
| 339 | * and only dehugify it if it's failed to meet the criteria for an | ||
| 340 | * extended period of time. When background threads are on, we should | ||
| 341 | * try to take this hit on one of them, as well. | ||
| 342 | * | ||
| 343 | * I think the ideal setting is THP always enabled, and defrag set to | ||
| 344 | * deferred; in that case we don't need any explicit calls on the | ||
| 345 | * allocator's end at all; we just try to pack allocations in a | ||
| 346 | * hugepage-friendly manner and let the OS hugify in the background. | ||
| 347 | */ | ||
| 348 | hpdata_purge_allowed_set(ps, hpdata_ndirty_get(ps) > 0); | ||
| 349 | if (hpa_good_hugification_candidate(shard, ps) | ||
| 350 | && !hpdata_huge_get(ps)) { | ||
| 351 | nstime_t now; | ||
| 352 | shard->central->hooks.curtime(&now, /* first_reading */ true); | ||
| 353 | hpdata_allow_hugify(ps, now); | ||
| 354 | } | ||
| 355 | /* | ||
| 356 | * Once a hugepage has become eligible for hugification, we don't mark | ||
| 357 | * it as ineligible just because it stops meeting the criteria (this | ||
| 358 | * could lead to situations where a hugepage that spends most of its | ||
| 359 | * time meeting the criteria never quite getting hugified if there are | ||
| 360 | * intervening deallocations). The idea is that the hugification delay | ||
| 361 | * will allow them to get purged, reseting their "hugify-allowed" bit. | ||
| 362 | * If they don't get purged, then the hugification isn't hurting and | ||
| 363 | * might help. As an exception, we don't hugify hugepages that are now | ||
| 364 | * empty; it definitely doesn't help there until the hugepage gets | ||
| 365 | * reused, which is likely not for a while. | ||
| 366 | */ | ||
| 367 | if (hpdata_nactive_get(ps) == 0) { | ||
| 368 | hpdata_disallow_hugify(ps); | ||
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 | static bool | ||
| 373 | hpa_shard_has_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 374 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 375 | hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); | ||
| 376 | return to_hugify != NULL || hpa_should_purge(tsdn, shard); | ||
| 377 | } | ||
| 378 | |||
| 379 | /* Returns whether or not we purged anything. */ | ||
| 380 | static bool | ||
| 381 | hpa_try_purge(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 382 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 383 | |||
| 384 | hpdata_t *to_purge = psset_pick_purge(&shard->psset); | ||
| 385 | if (to_purge == NULL) { | ||
| 386 | return false; | ||
| 387 | } | ||
| 388 | assert(hpdata_purge_allowed_get(to_purge)); | ||
| 389 | assert(!hpdata_changing_state_get(to_purge)); | ||
| 390 | |||
| 391 | /* | ||
| 392 | * Don't let anyone else purge or hugify this page while | ||
| 393 | * we're purging it (allocations and deallocations are | ||
| 394 | * OK). | ||
| 395 | */ | ||
| 396 | psset_update_begin(&shard->psset, to_purge); | ||
| 397 | assert(hpdata_alloc_allowed_get(to_purge)); | ||
| 398 | hpdata_mid_purge_set(to_purge, true); | ||
| 399 | hpdata_purge_allowed_set(to_purge, false); | ||
| 400 | hpdata_disallow_hugify(to_purge); | ||
| 401 | /* | ||
| 402 | * Unlike with hugification (where concurrent | ||
| 403 | * allocations are allowed), concurrent allocation out | ||
| 404 | * of a hugepage being purged is unsafe; we might hand | ||
| 405 | * out an extent for an allocation and then purge it | ||
| 406 | * (clearing out user data). | ||
| 407 | */ | ||
| 408 | hpdata_alloc_allowed_set(to_purge, false); | ||
| 409 | psset_update_end(&shard->psset, to_purge); | ||
| 410 | |||
| 411 | /* Gather all the metadata we'll need during the purge. */ | ||
| 412 | bool dehugify = hpdata_huge_get(to_purge); | ||
| 413 | hpdata_purge_state_t purge_state; | ||
| 414 | size_t num_to_purge = hpdata_purge_begin(to_purge, &purge_state); | ||
| 415 | |||
| 416 | shard->npending_purge += num_to_purge; | ||
| 417 | |||
| 418 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 419 | |||
| 420 | /* Actually do the purging, now that the lock is dropped. */ | ||
| 421 | if (dehugify) { | ||
| 422 | shard->central->hooks.dehugify(hpdata_addr_get(to_purge), | ||
| 423 | HUGEPAGE); | ||
| 424 | } | ||
| 425 | size_t total_purged = 0; | ||
| 426 | uint64_t purges_this_pass = 0; | ||
| 427 | void *purge_addr; | ||
| 428 | size_t purge_size; | ||
| 429 | while (hpdata_purge_next(to_purge, &purge_state, &purge_addr, | ||
| 430 | &purge_size)) { | ||
| 431 | total_purged += purge_size; | ||
| 432 | assert(total_purged <= HUGEPAGE); | ||
| 433 | purges_this_pass++; | ||
| 434 | shard->central->hooks.purge(purge_addr, purge_size); | ||
| 435 | } | ||
| 436 | |||
| 437 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 438 | /* The shard updates */ | ||
| 439 | shard->npending_purge -= num_to_purge; | ||
| 440 | shard->stats.npurge_passes++; | ||
| 441 | shard->stats.npurges += purges_this_pass; | ||
| 442 | shard->central->hooks.curtime(&shard->last_purge, | ||
| 443 | /* first_reading */ false); | ||
| 444 | if (dehugify) { | ||
| 445 | shard->stats.ndehugifies++; | ||
| 446 | } | ||
| 447 | |||
| 448 | /* The hpdata updates. */ | ||
| 449 | psset_update_begin(&shard->psset, to_purge); | ||
| 450 | if (dehugify) { | ||
| 451 | hpdata_dehugify(to_purge); | ||
| 452 | } | ||
| 453 | hpdata_purge_end(to_purge, &purge_state); | ||
| 454 | hpdata_mid_purge_set(to_purge, false); | ||
| 455 | |||
| 456 | hpdata_alloc_allowed_set(to_purge, true); | ||
| 457 | hpa_update_purge_hugify_eligibility(tsdn, shard, to_purge); | ||
| 458 | |||
| 459 | psset_update_end(&shard->psset, to_purge); | ||
| 460 | |||
| 461 | return true; | ||
| 462 | } | ||
| 463 | |||
| 464 | /* Returns whether or not we hugified anything. */ | ||
| 465 | static bool | ||
| 466 | hpa_try_hugify(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 467 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 468 | |||
| 469 | if (hpa_hugify_blocked_by_ndirty(tsdn, shard)) { | ||
| 470 | return false; | ||
| 471 | } | ||
| 472 | |||
| 473 | hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); | ||
| 474 | if (to_hugify == NULL) { | ||
| 475 | return false; | ||
| 476 | } | ||
| 477 | assert(hpdata_hugify_allowed_get(to_hugify)); | ||
| 478 | assert(!hpdata_changing_state_get(to_hugify)); | ||
| 479 | |||
| 480 | /* Make sure that it's been hugifiable for long enough. */ | ||
| 481 | nstime_t time_hugify_allowed = hpdata_time_hugify_allowed(to_hugify); | ||
| 482 | uint64_t millis = shard->central->hooks.ms_since(&time_hugify_allowed); | ||
| 483 | if (millis < shard->opts.hugify_delay_ms) { | ||
| 484 | return false; | ||
| 485 | } | ||
| 486 | |||
| 487 | /* | ||
| 488 | * Don't let anyone else purge or hugify this page while | ||
| 489 | * we're hugifying it (allocations and deallocations are | ||
| 490 | * OK). | ||
| 491 | */ | ||
| 492 | psset_update_begin(&shard->psset, to_hugify); | ||
| 493 | hpdata_mid_hugify_set(to_hugify, true); | ||
| 494 | hpdata_purge_allowed_set(to_hugify, false); | ||
| 495 | hpdata_disallow_hugify(to_hugify); | ||
| 496 | assert(hpdata_alloc_allowed_get(to_hugify)); | ||
| 497 | psset_update_end(&shard->psset, to_hugify); | ||
| 498 | |||
| 499 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 500 | |||
| 501 | shard->central->hooks.hugify(hpdata_addr_get(to_hugify), HUGEPAGE); | ||
| 502 | |||
| 503 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 504 | shard->stats.nhugifies++; | ||
| 505 | |||
| 506 | psset_update_begin(&shard->psset, to_hugify); | ||
| 507 | hpdata_hugify(to_hugify); | ||
| 508 | hpdata_mid_hugify_set(to_hugify, false); | ||
| 509 | hpa_update_purge_hugify_eligibility(tsdn, shard, to_hugify); | ||
| 510 | psset_update_end(&shard->psset, to_hugify); | ||
| 511 | |||
| 512 | return true; | ||
| 513 | } | ||
| 514 | |||
| 515 | /* | ||
| 516 | * Execution of deferred work is forced if it's triggered by an explicit | ||
| 517 | * hpa_shard_do_deferred_work() call. | ||
| 518 | */ | ||
| 519 | static void | ||
| 520 | hpa_shard_maybe_do_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard, | ||
| 521 | bool forced) { | ||
| 522 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 523 | if (!forced && shard->opts.deferral_allowed) { | ||
| 524 | return; | ||
| 525 | } | ||
| 526 | /* | ||
| 527 | * If we're on a background thread, do work so long as there's work to | ||
| 528 | * be done. Otherwise, bound latency to not be *too* bad by doing at | ||
| 529 | * most a small fixed number of operations. | ||
| 530 | */ | ||
| 531 | bool hugified = false; | ||
| 532 | bool purged = false; | ||
| 533 | size_t max_ops = (forced ? (size_t)-1 : 16); | ||
| 534 | size_t nops = 0; | ||
| 535 | do { | ||
| 536 | /* | ||
| 537 | * Always purge before hugifying, to make sure we get some | ||
| 538 | * ability to hit our quiescence targets. | ||
| 539 | */ | ||
| 540 | purged = false; | ||
| 541 | while (hpa_should_purge(tsdn, shard) && nops < max_ops) { | ||
| 542 | purged = hpa_try_purge(tsdn, shard); | ||
| 543 | if (purged) { | ||
| 544 | nops++; | ||
| 545 | } | ||
| 546 | } | ||
| 547 | hugified = hpa_try_hugify(tsdn, shard); | ||
| 548 | if (hugified) { | ||
| 549 | nops++; | ||
| 550 | } | ||
| 551 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 552 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 553 | } while ((hugified || purged) && nops < max_ops); | ||
| 554 | } | ||
| 555 | |||
| 556 | static edata_t * | ||
| 557 | hpa_try_alloc_one_no_grow(tsdn_t *tsdn, hpa_shard_t *shard, size_t size, | ||
| 558 | bool *oom) { | ||
| 559 | bool err; | ||
| 560 | edata_t *edata = edata_cache_fast_get(tsdn, &shard->ecf); | ||
| 561 | if (edata == NULL) { | ||
| 562 | *oom = true; | ||
| 563 | return NULL; | ||
| 564 | } | ||
| 565 | |||
| 566 | hpdata_t *ps = psset_pick_alloc(&shard->psset, size); | ||
| 567 | if (ps == NULL) { | ||
| 568 | edata_cache_fast_put(tsdn, &shard->ecf, edata); | ||
| 569 | return NULL; | ||
| 570 | } | ||
| 571 | |||
| 572 | psset_update_begin(&shard->psset, ps); | ||
| 573 | |||
| 574 | if (hpdata_empty(ps)) { | ||
| 575 | /* | ||
| 576 | * If the pageslab used to be empty, treat it as though it's | ||
| 577 | * brand new for fragmentation-avoidance purposes; what we're | ||
| 578 | * trying to approximate is the age of the allocations *in* that | ||
| 579 | * pageslab, and the allocations in the new pageslab are | ||
| 580 | * definitionally the youngest in this hpa shard. | ||
| 581 | */ | ||
| 582 | hpdata_age_set(ps, shard->age_counter++); | ||
| 583 | } | ||
| 584 | |||
| 585 | void *addr = hpdata_reserve_alloc(ps, size); | ||
| 586 | edata_init(edata, shard->ind, addr, size, /* slab */ false, | ||
| 587 | SC_NSIZES, /* sn */ hpdata_age_get(ps), extent_state_active, | ||
| 588 | /* zeroed */ false, /* committed */ true, EXTENT_PAI_HPA, | ||
| 589 | EXTENT_NOT_HEAD); | ||
| 590 | edata_ps_set(edata, ps); | ||
| 591 | |||
| 592 | /* | ||
| 593 | * This could theoretically be moved outside of the critical section, | ||
| 594 | * but that introduces the potential for a race. Without the lock, the | ||
| 595 | * (initially nonempty, since this is the reuse pathway) pageslab we | ||
| 596 | * allocated out of could become otherwise empty while the lock is | ||
| 597 | * dropped. This would force us to deal with a pageslab eviction down | ||
| 598 | * the error pathway, which is a pain. | ||
| 599 | */ | ||
| 600 | err = emap_register_boundary(tsdn, shard->emap, edata, | ||
| 601 | SC_NSIZES, /* slab */ false); | ||
| 602 | if (err) { | ||
| 603 | hpdata_unreserve(ps, edata_addr_get(edata), | ||
| 604 | edata_size_get(edata)); | ||
| 605 | /* | ||
| 606 | * We should arguably reset dirty state here, but this would | ||
| 607 | * require some sort of prepare + commit functionality that's a | ||
| 608 | * little much to deal with for now. | ||
| 609 | * | ||
| 610 | * We don't have a do_deferred_work down this pathway, on the | ||
| 611 | * principle that we didn't *really* affect shard state (we | ||
| 612 | * tweaked the stats, but our tweaks weren't really accurate). | ||
| 613 | */ | ||
| 614 | psset_update_end(&shard->psset, ps); | ||
| 615 | edata_cache_fast_put(tsdn, &shard->ecf, edata); | ||
| 616 | *oom = true; | ||
| 617 | return NULL; | ||
| 618 | } | ||
| 619 | |||
| 620 | hpa_update_purge_hugify_eligibility(tsdn, shard, ps); | ||
| 621 | psset_update_end(&shard->psset, ps); | ||
| 622 | return edata; | ||
| 623 | } | ||
| 624 | |||
| 625 | static size_t | ||
| 626 | hpa_try_alloc_batch_no_grow(tsdn_t *tsdn, hpa_shard_t *shard, size_t size, | ||
| 627 | bool *oom, size_t nallocs, edata_list_active_t *results, | ||
| 628 | bool *deferred_work_generated) { | ||
| 629 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 630 | size_t nsuccess = 0; | ||
| 631 | for (; nsuccess < nallocs; nsuccess++) { | ||
| 632 | edata_t *edata = hpa_try_alloc_one_no_grow(tsdn, shard, size, | ||
| 633 | oom); | ||
| 634 | if (edata == NULL) { | ||
| 635 | break; | ||
| 636 | } | ||
| 637 | edata_list_active_append(results, edata); | ||
| 638 | } | ||
| 639 | |||
| 640 | hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ false); | ||
| 641 | *deferred_work_generated = hpa_shard_has_deferred_work(tsdn, shard); | ||
| 642 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 643 | return nsuccess; | ||
| 644 | } | ||
| 645 | |||
| 646 | static size_t | ||
| 647 | hpa_alloc_batch_psset(tsdn_t *tsdn, hpa_shard_t *shard, size_t size, | ||
| 648 | size_t nallocs, edata_list_active_t *results, | ||
| 649 | bool *deferred_work_generated) { | ||
| 650 | assert(size <= shard->opts.slab_max_alloc); | ||
| 651 | bool oom = false; | ||
| 652 | |||
| 653 | size_t nsuccess = hpa_try_alloc_batch_no_grow(tsdn, shard, size, &oom, | ||
| 654 | nallocs, results, deferred_work_generated); | ||
| 655 | |||
| 656 | if (nsuccess == nallocs || oom) { | ||
| 657 | return nsuccess; | ||
| 658 | } | ||
| 659 | |||
| 660 | /* | ||
| 661 | * We didn't OOM, but weren't able to fill everything requested of us; | ||
| 662 | * try to grow. | ||
| 663 | */ | ||
| 664 | malloc_mutex_lock(tsdn, &shard->grow_mtx); | ||
| 665 | /* | ||
| 666 | * Check for grow races; maybe some earlier thread expanded the psset | ||
| 667 | * in between when we dropped the main mutex and grabbed the grow mutex. | ||
| 668 | */ | ||
| 669 | nsuccess += hpa_try_alloc_batch_no_grow(tsdn, shard, size, &oom, | ||
| 670 | nallocs - nsuccess, results, deferred_work_generated); | ||
| 671 | if (nsuccess == nallocs || oom) { | ||
| 672 | malloc_mutex_unlock(tsdn, &shard->grow_mtx); | ||
| 673 | return nsuccess; | ||
| 674 | } | ||
| 675 | |||
| 676 | /* | ||
| 677 | * Note that we don't hold shard->mtx here (while growing); | ||
| 678 | * deallocations (and allocations of smaller sizes) may still succeed | ||
| 679 | * while we're doing this potentially expensive system call. | ||
| 680 | */ | ||
| 681 | hpdata_t *ps = hpa_central_extract(tsdn, shard->central, size, &oom); | ||
| 682 | if (ps == NULL) { | ||
| 683 | malloc_mutex_unlock(tsdn, &shard->grow_mtx); | ||
| 684 | return nsuccess; | ||
| 685 | } | ||
| 686 | |||
| 687 | /* | ||
| 688 | * We got the pageslab; allocate from it. This does an unlock followed | ||
| 689 | * by a lock on the same mutex, and holds the grow mutex while doing | ||
| 690 | * deferred work, but this is an uncommon path; the simplicity is worth | ||
| 691 | * it. | ||
| 692 | */ | ||
| 693 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 694 | psset_insert(&shard->psset, ps); | ||
| 695 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 696 | |||
| 697 | nsuccess += hpa_try_alloc_batch_no_grow(tsdn, shard, size, &oom, | ||
| 698 | nallocs - nsuccess, results, deferred_work_generated); | ||
| 699 | /* | ||
| 700 | * Drop grow_mtx before doing deferred work; other threads blocked on it | ||
| 701 | * should be allowed to proceed while we're working. | ||
| 702 | */ | ||
| 703 | malloc_mutex_unlock(tsdn, &shard->grow_mtx); | ||
| 704 | |||
| 705 | return nsuccess; | ||
| 706 | } | ||
| 707 | |||
| 708 | static hpa_shard_t * | ||
| 709 | hpa_from_pai(pai_t *self) { | ||
| 710 | assert(self->alloc = &hpa_alloc); | ||
| 711 | assert(self->expand = &hpa_expand); | ||
| 712 | assert(self->shrink = &hpa_shrink); | ||
| 713 | assert(self->dalloc = &hpa_dalloc); | ||
| 714 | return (hpa_shard_t *)self; | ||
| 715 | } | ||
| 716 | |||
| 717 | static size_t | ||
| 718 | hpa_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, | ||
| 719 | edata_list_active_t *results, bool *deferred_work_generated) { | ||
| 720 | assert(nallocs > 0); | ||
| 721 | assert((size & PAGE_MASK) == 0); | ||
| 722 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 723 | WITNESS_RANK_CORE, 0); | ||
| 724 | hpa_shard_t *shard = hpa_from_pai(self); | ||
| 725 | |||
| 726 | if (size > shard->opts.slab_max_alloc) { | ||
| 727 | return 0; | ||
| 728 | } | ||
| 729 | |||
| 730 | size_t nsuccess = hpa_alloc_batch_psset(tsdn, shard, size, nallocs, | ||
| 731 | results, deferred_work_generated); | ||
| 732 | |||
| 733 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 734 | WITNESS_RANK_CORE, 0); | ||
| 735 | |||
| 736 | /* | ||
| 737 | * Guard the sanity checks with config_debug because the loop cannot be | ||
| 738 | * proven non-circular by the compiler, even if everything within the | ||
| 739 | * loop is optimized away. | ||
| 740 | */ | ||
| 741 | if (config_debug) { | ||
| 742 | edata_t *edata; | ||
| 743 | ql_foreach(edata, &results->head, ql_link_active) { | ||
| 744 | emap_assert_mapped(tsdn, shard->emap, edata); | ||
| 745 | assert(edata_pai_get(edata) == EXTENT_PAI_HPA); | ||
| 746 | assert(edata_state_get(edata) == extent_state_active); | ||
| 747 | assert(edata_arena_ind_get(edata) == shard->ind); | ||
| 748 | assert(edata_szind_get_maybe_invalid(edata) == | ||
| 749 | SC_NSIZES); | ||
| 750 | assert(!edata_slab_get(edata)); | ||
| 751 | assert(edata_committed_get(edata)); | ||
| 752 | assert(edata_base_get(edata) == edata_addr_get(edata)); | ||
| 753 | assert(edata_base_get(edata) != NULL); | ||
| 754 | } | ||
| 755 | } | ||
| 756 | return nsuccess; | ||
| 757 | } | ||
| 758 | |||
| 759 | static edata_t * | ||
| 760 | hpa_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, | ||
| 761 | bool guarded, bool frequent_reuse, bool *deferred_work_generated) { | ||
| 762 | assert((size & PAGE_MASK) == 0); | ||
| 763 | assert(!guarded); | ||
| 764 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 765 | WITNESS_RANK_CORE, 0); | ||
| 766 | |||
| 767 | /* We don't handle alignment or zeroing for now. */ | ||
| 768 | if (alignment > PAGE || zero) { | ||
| 769 | return NULL; | ||
| 770 | } | ||
| 771 | /* | ||
| 772 | * An alloc with alignment == PAGE and zero == false is equivalent to a | ||
| 773 | * batch alloc of 1. Just do that, so we can share code. | ||
| 774 | */ | ||
| 775 | edata_list_active_t results; | ||
| 776 | edata_list_active_init(&results); | ||
| 777 | size_t nallocs = hpa_alloc_batch(tsdn, self, size, /* nallocs */ 1, | ||
| 778 | &results, deferred_work_generated); | ||
| 779 | assert(nallocs == 0 || nallocs == 1); | ||
| 780 | edata_t *edata = edata_list_active_first(&results); | ||
| 781 | return edata; | ||
| 782 | } | ||
| 783 | |||
| 784 | static bool | ||
| 785 | hpa_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, | ||
| 786 | size_t new_size, bool zero, bool *deferred_work_generated) { | ||
| 787 | /* Expand not yet supported. */ | ||
| 788 | return true; | ||
| 789 | } | ||
| 790 | |||
| 791 | static bool | ||
| 792 | hpa_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 793 | size_t old_size, size_t new_size, bool *deferred_work_generated) { | ||
| 794 | /* Shrink not yet supported. */ | ||
| 795 | return true; | ||
| 796 | } | ||
| 797 | |||
| 798 | static void | ||
| 799 | hpa_dalloc_prepare_unlocked(tsdn_t *tsdn, hpa_shard_t *shard, edata_t *edata) { | ||
| 800 | malloc_mutex_assert_not_owner(tsdn, &shard->mtx); | ||
| 801 | |||
| 802 | assert(edata_pai_get(edata) == EXTENT_PAI_HPA); | ||
| 803 | assert(edata_state_get(edata) == extent_state_active); | ||
| 804 | assert(edata_arena_ind_get(edata) == shard->ind); | ||
| 805 | assert(edata_szind_get_maybe_invalid(edata) == SC_NSIZES); | ||
| 806 | assert(edata_committed_get(edata)); | ||
| 807 | assert(edata_base_get(edata) != NULL); | ||
| 808 | |||
| 809 | /* | ||
| 810 | * Another thread shouldn't be trying to touch the metadata of an | ||
| 811 | * allocation being freed. The one exception is a merge attempt from a | ||
| 812 | * lower-addressed PAC extent; in this case we have a nominal race on | ||
| 813 | * the edata metadata bits, but in practice the fact that the PAI bits | ||
| 814 | * are different will prevent any further access. The race is bad, but | ||
| 815 | * benign in practice, and the long term plan is to track enough state | ||
| 816 | * in the rtree to prevent these merge attempts in the first place. | ||
| 817 | */ | ||
| 818 | edata_addr_set(edata, edata_base_get(edata)); | ||
| 819 | edata_zeroed_set(edata, false); | ||
| 820 | emap_deregister_boundary(tsdn, shard->emap, edata); | ||
| 821 | } | ||
| 822 | |||
| 823 | static void | ||
| 824 | hpa_dalloc_locked(tsdn_t *tsdn, hpa_shard_t *shard, edata_t *edata) { | ||
| 825 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 826 | |||
| 827 | /* | ||
| 828 | * Release the metadata early, to avoid having to remember to do it | ||
| 829 | * while we're also doing tricky purging logic. First, we need to grab | ||
| 830 | * a few bits of metadata from it. | ||
| 831 | * | ||
| 832 | * Note that the shard mutex protects ps's metadata too; it wouldn't be | ||
| 833 | * correct to try to read most information out of it without the lock. | ||
| 834 | */ | ||
| 835 | hpdata_t *ps = edata_ps_get(edata); | ||
| 836 | /* Currently, all edatas come from pageslabs. */ | ||
| 837 | assert(ps != NULL); | ||
| 838 | void *unreserve_addr = edata_addr_get(edata); | ||
| 839 | size_t unreserve_size = edata_size_get(edata); | ||
| 840 | edata_cache_fast_put(tsdn, &shard->ecf, edata); | ||
| 841 | |||
| 842 | psset_update_begin(&shard->psset, ps); | ||
| 843 | hpdata_unreserve(ps, unreserve_addr, unreserve_size); | ||
| 844 | hpa_update_purge_hugify_eligibility(tsdn, shard, ps); | ||
| 845 | psset_update_end(&shard->psset, ps); | ||
| 846 | } | ||
| 847 | |||
| 848 | static void | ||
| 849 | hpa_dalloc_batch(tsdn_t *tsdn, pai_t *self, edata_list_active_t *list, | ||
| 850 | bool *deferred_work_generated) { | ||
| 851 | hpa_shard_t *shard = hpa_from_pai(self); | ||
| 852 | |||
| 853 | edata_t *edata; | ||
| 854 | ql_foreach(edata, &list->head, ql_link_active) { | ||
| 855 | hpa_dalloc_prepare_unlocked(tsdn, shard, edata); | ||
| 856 | } | ||
| 857 | |||
| 858 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 859 | /* Now, remove from the list. */ | ||
| 860 | while ((edata = edata_list_active_first(list)) != NULL) { | ||
| 861 | edata_list_active_remove(list, edata); | ||
| 862 | hpa_dalloc_locked(tsdn, shard, edata); | ||
| 863 | } | ||
| 864 | hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ false); | ||
| 865 | *deferred_work_generated = | ||
| 866 | hpa_shard_has_deferred_work(tsdn, shard); | ||
| 867 | |||
| 868 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 869 | } | ||
| 870 | |||
| 871 | static void | ||
| 872 | hpa_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 873 | bool *deferred_work_generated) { | ||
| 874 | assert(!edata_guarded_get(edata)); | ||
| 875 | /* Just a dalloc_batch of size 1; this lets us share logic. */ | ||
| 876 | edata_list_active_t dalloc_list; | ||
| 877 | edata_list_active_init(&dalloc_list); | ||
| 878 | edata_list_active_append(&dalloc_list, edata); | ||
| 879 | hpa_dalloc_batch(tsdn, self, &dalloc_list, deferred_work_generated); | ||
| 880 | } | ||
| 881 | |||
| 882 | /* | ||
| 883 | * Calculate time until either purging or hugification ought to happen. | ||
| 884 | * Called by background threads. | ||
| 885 | */ | ||
| 886 | static uint64_t | ||
| 887 | hpa_time_until_deferred_work(tsdn_t *tsdn, pai_t *self) { | ||
| 888 | hpa_shard_t *shard = hpa_from_pai(self); | ||
| 889 | uint64_t time_ns = BACKGROUND_THREAD_DEFERRED_MAX; | ||
| 890 | |||
| 891 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 892 | |||
| 893 | hpdata_t *to_hugify = psset_pick_hugify(&shard->psset); | ||
| 894 | if (to_hugify != NULL) { | ||
| 895 | nstime_t time_hugify_allowed = | ||
| 896 | hpdata_time_hugify_allowed(to_hugify); | ||
| 897 | uint64_t since_hugify_allowed_ms = | ||
| 898 | shard->central->hooks.ms_since(&time_hugify_allowed); | ||
| 899 | /* | ||
| 900 | * If not enough time has passed since hugification was allowed, | ||
| 901 | * sleep for the rest. | ||
| 902 | */ | ||
| 903 | if (since_hugify_allowed_ms < shard->opts.hugify_delay_ms) { | ||
| 904 | time_ns = shard->opts.hugify_delay_ms - | ||
| 905 | since_hugify_allowed_ms; | ||
| 906 | time_ns *= 1000 * 1000; | ||
| 907 | } else { | ||
| 908 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 909 | return BACKGROUND_THREAD_DEFERRED_MIN; | ||
| 910 | } | ||
| 911 | } | ||
| 912 | |||
| 913 | if (hpa_should_purge(tsdn, shard)) { | ||
| 914 | /* | ||
| 915 | * If we haven't purged before, no need to check interval | ||
| 916 | * between purges. Simply purge as soon as possible. | ||
| 917 | */ | ||
| 918 | if (shard->stats.npurge_passes == 0) { | ||
| 919 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 920 | return BACKGROUND_THREAD_DEFERRED_MIN; | ||
| 921 | } | ||
| 922 | uint64_t since_last_purge_ms = shard->central->hooks.ms_since( | ||
| 923 | &shard->last_purge); | ||
| 924 | |||
| 925 | if (since_last_purge_ms < shard->opts.min_purge_interval_ms) { | ||
| 926 | uint64_t until_purge_ns; | ||
| 927 | until_purge_ns = shard->opts.min_purge_interval_ms - | ||
| 928 | since_last_purge_ms; | ||
| 929 | until_purge_ns *= 1000 * 1000; | ||
| 930 | |||
| 931 | if (until_purge_ns < time_ns) { | ||
| 932 | time_ns = until_purge_ns; | ||
| 933 | } | ||
| 934 | } else { | ||
| 935 | time_ns = BACKGROUND_THREAD_DEFERRED_MIN; | ||
| 936 | } | ||
| 937 | } | ||
| 938 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 939 | return time_ns; | ||
| 940 | } | ||
| 941 | |||
| 942 | void | ||
| 943 | hpa_shard_disable(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 944 | hpa_do_consistency_checks(shard); | ||
| 945 | |||
| 946 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 947 | edata_cache_fast_disable(tsdn, &shard->ecf); | ||
| 948 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 949 | } | ||
| 950 | |||
| 951 | static void | ||
| 952 | hpa_shard_assert_stats_empty(psset_bin_stats_t *bin_stats) { | ||
| 953 | assert(bin_stats->npageslabs == 0); | ||
| 954 | assert(bin_stats->nactive == 0); | ||
| 955 | } | ||
| 956 | |||
| 957 | static void | ||
| 958 | hpa_assert_empty(tsdn_t *tsdn, hpa_shard_t *shard, psset_t *psset) { | ||
| 959 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 960 | for (int huge = 0; huge <= 1; huge++) { | ||
| 961 | hpa_shard_assert_stats_empty(&psset->stats.full_slabs[huge]); | ||
| 962 | for (pszind_t i = 0; i < PSSET_NPSIZES; i++) { | ||
| 963 | hpa_shard_assert_stats_empty( | ||
| 964 | &psset->stats.nonfull_slabs[i][huge]); | ||
| 965 | } | ||
| 966 | } | ||
| 967 | } | ||
| 968 | |||
| 969 | void | ||
| 970 | hpa_shard_destroy(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 971 | hpa_do_consistency_checks(shard); | ||
| 972 | /* | ||
| 973 | * By the time we're here, the arena code should have dalloc'd all the | ||
| 974 | * active extents, which means we should have eventually evicted | ||
| 975 | * everything from the psset, so it shouldn't be able to serve even a | ||
| 976 | * 1-page allocation. | ||
| 977 | */ | ||
| 978 | if (config_debug) { | ||
| 979 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 980 | hpa_assert_empty(tsdn, shard, &shard->psset); | ||
| 981 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 982 | } | ||
| 983 | hpdata_t *ps; | ||
| 984 | while ((ps = psset_pick_alloc(&shard->psset, PAGE)) != NULL) { | ||
| 985 | /* There should be no allocations anywhere. */ | ||
| 986 | assert(hpdata_empty(ps)); | ||
| 987 | psset_remove(&shard->psset, ps); | ||
| 988 | shard->central->hooks.unmap(hpdata_addr_get(ps), HUGEPAGE); | ||
| 989 | } | ||
| 990 | } | ||
| 991 | |||
| 992 | void | ||
| 993 | hpa_shard_set_deferral_allowed(tsdn_t *tsdn, hpa_shard_t *shard, | ||
| 994 | bool deferral_allowed) { | ||
| 995 | hpa_do_consistency_checks(shard); | ||
| 996 | |||
| 997 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 998 | bool deferral_previously_allowed = shard->opts.deferral_allowed; | ||
| 999 | shard->opts.deferral_allowed = deferral_allowed; | ||
| 1000 | if (deferral_previously_allowed && !deferral_allowed) { | ||
| 1001 | hpa_shard_maybe_do_deferred_work(tsdn, shard, | ||
| 1002 | /* forced */ true); | ||
| 1003 | } | ||
| 1004 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | void | ||
| 1008 | hpa_shard_do_deferred_work(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 1009 | hpa_do_consistency_checks(shard); | ||
| 1010 | |||
| 1011 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 1012 | hpa_shard_maybe_do_deferred_work(tsdn, shard, /* forced */ true); | ||
| 1013 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | void | ||
| 1017 | hpa_shard_prefork3(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 1018 | hpa_do_consistency_checks(shard); | ||
| 1019 | |||
| 1020 | malloc_mutex_prefork(tsdn, &shard->grow_mtx); | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | void | ||
| 1024 | hpa_shard_prefork4(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 1025 | hpa_do_consistency_checks(shard); | ||
| 1026 | |||
| 1027 | malloc_mutex_prefork(tsdn, &shard->mtx); | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | void | ||
| 1031 | hpa_shard_postfork_parent(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 1032 | hpa_do_consistency_checks(shard); | ||
| 1033 | |||
| 1034 | malloc_mutex_postfork_parent(tsdn, &shard->grow_mtx); | ||
| 1035 | malloc_mutex_postfork_parent(tsdn, &shard->mtx); | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | void | ||
| 1039 | hpa_shard_postfork_child(tsdn_t *tsdn, hpa_shard_t *shard) { | ||
| 1040 | hpa_do_consistency_checks(shard); | ||
| 1041 | |||
| 1042 | malloc_mutex_postfork_child(tsdn, &shard->grow_mtx); | ||
| 1043 | malloc_mutex_postfork_child(tsdn, &shard->mtx); | ||
| 1044 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/hpa_hooks.c b/examples/redis-unstable/deps/jemalloc/src/hpa_hooks.c deleted file mode 100644 index ade581e..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/hpa_hooks.c +++ /dev/null | |||
| @@ -1,63 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/hpa_hooks.h" | ||
| 5 | |||
| 6 | static void *hpa_hooks_map(size_t size); | ||
| 7 | static void hpa_hooks_unmap(void *ptr, size_t size); | ||
| 8 | static void hpa_hooks_purge(void *ptr, size_t size); | ||
| 9 | static void hpa_hooks_hugify(void *ptr, size_t size); | ||
| 10 | static void hpa_hooks_dehugify(void *ptr, size_t size); | ||
| 11 | static void hpa_hooks_curtime(nstime_t *r_nstime, bool first_reading); | ||
| 12 | static uint64_t hpa_hooks_ms_since(nstime_t *past_nstime); | ||
| 13 | |||
| 14 | hpa_hooks_t hpa_hooks_default = { | ||
| 15 | &hpa_hooks_map, | ||
| 16 | &hpa_hooks_unmap, | ||
| 17 | &hpa_hooks_purge, | ||
| 18 | &hpa_hooks_hugify, | ||
| 19 | &hpa_hooks_dehugify, | ||
| 20 | &hpa_hooks_curtime, | ||
| 21 | &hpa_hooks_ms_since | ||
| 22 | }; | ||
| 23 | |||
| 24 | static void * | ||
| 25 | hpa_hooks_map(size_t size) { | ||
| 26 | bool commit = true; | ||
| 27 | return pages_map(NULL, size, HUGEPAGE, &commit); | ||
| 28 | } | ||
| 29 | |||
| 30 | static void | ||
| 31 | hpa_hooks_unmap(void *ptr, size_t size) { | ||
| 32 | pages_unmap(ptr, size); | ||
| 33 | } | ||
| 34 | |||
| 35 | static void | ||
| 36 | hpa_hooks_purge(void *ptr, size_t size) { | ||
| 37 | pages_purge_forced(ptr, size); | ||
| 38 | } | ||
| 39 | |||
| 40 | static void | ||
| 41 | hpa_hooks_hugify(void *ptr, size_t size) { | ||
| 42 | bool err = pages_huge(ptr, size); | ||
| 43 | (void)err; | ||
| 44 | } | ||
| 45 | |||
| 46 | static void | ||
| 47 | hpa_hooks_dehugify(void *ptr, size_t size) { | ||
| 48 | bool err = pages_nohuge(ptr, size); | ||
| 49 | (void)err; | ||
| 50 | } | ||
| 51 | |||
| 52 | static void | ||
| 53 | hpa_hooks_curtime(nstime_t *r_nstime, bool first_reading) { | ||
| 54 | if (first_reading) { | ||
| 55 | nstime_init_zero(r_nstime); | ||
| 56 | } | ||
| 57 | nstime_update(r_nstime); | ||
| 58 | } | ||
| 59 | |||
| 60 | static uint64_t | ||
| 61 | hpa_hooks_ms_since(nstime_t *past_nstime) { | ||
| 62 | return nstime_ns_since(past_nstime) / 1000 / 1000; | ||
| 63 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/hpdata.c b/examples/redis-unstable/deps/jemalloc/src/hpdata.c deleted file mode 100644 index e7d7294..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/hpdata.c +++ /dev/null | |||
| @@ -1,325 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/hpdata.h" | ||
| 5 | |||
| 6 | static int | ||
| 7 | hpdata_age_comp(const hpdata_t *a, const hpdata_t *b) { | ||
| 8 | uint64_t a_age = hpdata_age_get(a); | ||
| 9 | uint64_t b_age = hpdata_age_get(b); | ||
| 10 | /* | ||
| 11 | * hpdata ages are operation counts in the psset; no two should be the | ||
| 12 | * same. | ||
| 13 | */ | ||
| 14 | assert(a_age != b_age); | ||
| 15 | return (a_age > b_age) - (a_age < b_age); | ||
| 16 | } | ||
| 17 | |||
| 18 | ph_gen(, hpdata_age_heap, hpdata_t, age_link, hpdata_age_comp) | ||
| 19 | |||
| 20 | void | ||
| 21 | hpdata_init(hpdata_t *hpdata, void *addr, uint64_t age) { | ||
| 22 | hpdata_addr_set(hpdata, addr); | ||
| 23 | hpdata_age_set(hpdata, age); | ||
| 24 | hpdata->h_huge = false; | ||
| 25 | hpdata->h_alloc_allowed = true; | ||
| 26 | hpdata->h_in_psset_alloc_container = false; | ||
| 27 | hpdata->h_purge_allowed = false; | ||
| 28 | hpdata->h_hugify_allowed = false; | ||
| 29 | hpdata->h_in_psset_hugify_container = false; | ||
| 30 | hpdata->h_mid_purge = false; | ||
| 31 | hpdata->h_mid_hugify = false; | ||
| 32 | hpdata->h_updating = false; | ||
| 33 | hpdata->h_in_psset = false; | ||
| 34 | hpdata_longest_free_range_set(hpdata, HUGEPAGE_PAGES); | ||
| 35 | hpdata->h_nactive = 0; | ||
| 36 | fb_init(hpdata->active_pages, HUGEPAGE_PAGES); | ||
| 37 | hpdata->h_ntouched = 0; | ||
| 38 | fb_init(hpdata->touched_pages, HUGEPAGE_PAGES); | ||
| 39 | |||
| 40 | hpdata_assert_consistent(hpdata); | ||
| 41 | } | ||
| 42 | |||
| 43 | void * | ||
| 44 | hpdata_reserve_alloc(hpdata_t *hpdata, size_t sz) { | ||
| 45 | hpdata_assert_consistent(hpdata); | ||
| 46 | /* | ||
| 47 | * This is a metadata change; the hpdata should therefore either not be | ||
| 48 | * in the psset, or should have explicitly marked itself as being | ||
| 49 | * mid-update. | ||
| 50 | */ | ||
| 51 | assert(!hpdata->h_in_psset || hpdata->h_updating); | ||
| 52 | assert(hpdata->h_alloc_allowed); | ||
| 53 | assert((sz & PAGE_MASK) == 0); | ||
| 54 | size_t npages = sz >> LG_PAGE; | ||
| 55 | assert(npages <= hpdata_longest_free_range_get(hpdata)); | ||
| 56 | |||
| 57 | size_t result; | ||
| 58 | |||
| 59 | size_t start = 0; | ||
| 60 | /* | ||
| 61 | * These are dead stores, but the compiler will issue warnings on them | ||
| 62 | * since it can't tell statically that found is always true below. | ||
| 63 | */ | ||
| 64 | size_t begin = 0; | ||
| 65 | size_t len = 0; | ||
| 66 | |||
| 67 | size_t largest_unchosen_range = 0; | ||
| 68 | while (true) { | ||
| 69 | bool found = fb_urange_iter(hpdata->active_pages, | ||
| 70 | HUGEPAGE_PAGES, start, &begin, &len); | ||
| 71 | /* | ||
| 72 | * A precondition to this function is that hpdata must be able | ||
| 73 | * to serve the allocation. | ||
| 74 | */ | ||
| 75 | assert(found); | ||
| 76 | assert(len <= hpdata_longest_free_range_get(hpdata)); | ||
| 77 | if (len >= npages) { | ||
| 78 | /* | ||
| 79 | * We use first-fit within the page slabs; this gives | ||
| 80 | * bounded worst-case fragmentation within a slab. It's | ||
| 81 | * not necessarily right; we could experiment with | ||
| 82 | * various other options. | ||
| 83 | */ | ||
| 84 | break; | ||
| 85 | } | ||
| 86 | if (len > largest_unchosen_range) { | ||
| 87 | largest_unchosen_range = len; | ||
| 88 | } | ||
| 89 | start = begin + len; | ||
| 90 | } | ||
| 91 | /* We found a range; remember it. */ | ||
| 92 | result = begin; | ||
| 93 | fb_set_range(hpdata->active_pages, HUGEPAGE_PAGES, begin, npages); | ||
| 94 | hpdata->h_nactive += npages; | ||
| 95 | |||
| 96 | /* | ||
| 97 | * We might be about to dirty some memory for the first time; update our | ||
| 98 | * count if so. | ||
| 99 | */ | ||
| 100 | size_t new_dirty = fb_ucount(hpdata->touched_pages, HUGEPAGE_PAGES, | ||
| 101 | result, npages); | ||
| 102 | fb_set_range(hpdata->touched_pages, HUGEPAGE_PAGES, result, npages); | ||
| 103 | hpdata->h_ntouched += new_dirty; | ||
| 104 | |||
| 105 | /* | ||
| 106 | * If we allocated out of a range that was the longest in the hpdata, it | ||
| 107 | * might be the only one of that size and we'll have to adjust the | ||
| 108 | * metadata. | ||
| 109 | */ | ||
| 110 | if (len == hpdata_longest_free_range_get(hpdata)) { | ||
| 111 | start = begin + npages; | ||
| 112 | while (start < HUGEPAGE_PAGES) { | ||
| 113 | bool found = fb_urange_iter(hpdata->active_pages, | ||
| 114 | HUGEPAGE_PAGES, start, &begin, &len); | ||
| 115 | if (!found) { | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | assert(len <= hpdata_longest_free_range_get(hpdata)); | ||
| 119 | if (len == hpdata_longest_free_range_get(hpdata)) { | ||
| 120 | largest_unchosen_range = len; | ||
| 121 | break; | ||
| 122 | } | ||
| 123 | if (len > largest_unchosen_range) { | ||
| 124 | largest_unchosen_range = len; | ||
| 125 | } | ||
| 126 | start = begin + len; | ||
| 127 | } | ||
| 128 | hpdata_longest_free_range_set(hpdata, largest_unchosen_range); | ||
| 129 | } | ||
| 130 | |||
| 131 | hpdata_assert_consistent(hpdata); | ||
| 132 | return (void *)( | ||
| 133 | (uintptr_t)hpdata_addr_get(hpdata) + (result << LG_PAGE)); | ||
| 134 | } | ||
| 135 | |||
| 136 | void | ||
| 137 | hpdata_unreserve(hpdata_t *hpdata, void *addr, size_t sz) { | ||
| 138 | hpdata_assert_consistent(hpdata); | ||
| 139 | /* See the comment in reserve. */ | ||
| 140 | assert(!hpdata->h_in_psset || hpdata->h_updating); | ||
| 141 | assert(((uintptr_t)addr & PAGE_MASK) == 0); | ||
| 142 | assert((sz & PAGE_MASK) == 0); | ||
| 143 | size_t begin = ((uintptr_t)addr - (uintptr_t)hpdata_addr_get(hpdata)) | ||
| 144 | >> LG_PAGE; | ||
| 145 | assert(begin < HUGEPAGE_PAGES); | ||
| 146 | size_t npages = sz >> LG_PAGE; | ||
| 147 | size_t old_longest_range = hpdata_longest_free_range_get(hpdata); | ||
| 148 | |||
| 149 | fb_unset_range(hpdata->active_pages, HUGEPAGE_PAGES, begin, npages); | ||
| 150 | /* We might have just created a new, larger range. */ | ||
| 151 | size_t new_begin = (fb_fls(hpdata->active_pages, HUGEPAGE_PAGES, | ||
| 152 | begin) + 1); | ||
| 153 | size_t new_end = fb_ffs(hpdata->active_pages, HUGEPAGE_PAGES, | ||
| 154 | begin + npages - 1); | ||
| 155 | size_t new_range_len = new_end - new_begin; | ||
| 156 | |||
| 157 | if (new_range_len > old_longest_range) { | ||
| 158 | hpdata_longest_free_range_set(hpdata, new_range_len); | ||
| 159 | } | ||
| 160 | |||
| 161 | hpdata->h_nactive -= npages; | ||
| 162 | |||
| 163 | hpdata_assert_consistent(hpdata); | ||
| 164 | } | ||
| 165 | |||
| 166 | size_t | ||
| 167 | hpdata_purge_begin(hpdata_t *hpdata, hpdata_purge_state_t *purge_state) { | ||
| 168 | hpdata_assert_consistent(hpdata); | ||
| 169 | /* | ||
| 170 | * See the comment below; we might purge any inactive extent, so it's | ||
| 171 | * unsafe for any other thread to turn any inactive extent active while | ||
| 172 | * we're operating on it. | ||
| 173 | */ | ||
| 174 | assert(!hpdata_alloc_allowed_get(hpdata)); | ||
| 175 | |||
| 176 | purge_state->npurged = 0; | ||
| 177 | purge_state->next_purge_search_begin = 0; | ||
| 178 | |||
| 179 | /* | ||
| 180 | * Initialize to_purge. | ||
| 181 | * | ||
| 182 | * It's possible to end up in situations where two dirty extents are | ||
| 183 | * separated by a retained extent: | ||
| 184 | * - 1 page allocated. | ||
| 185 | * - 1 page allocated. | ||
| 186 | * - 1 pages allocated. | ||
| 187 | * | ||
| 188 | * If the middle page is freed and purged, and then the first and third | ||
| 189 | * pages are freed, and then another purge pass happens, the hpdata | ||
| 190 | * looks like this: | ||
| 191 | * - 1 page dirty. | ||
| 192 | * - 1 page retained. | ||
| 193 | * - 1 page dirty. | ||
| 194 | * | ||
| 195 | * But it's safe to do a single 3-page purge. | ||
| 196 | * | ||
| 197 | * We do this by first computing the dirty pages, and then filling in | ||
| 198 | * any gaps by extending each range in the dirty bitmap to extend until | ||
| 199 | * the next active page. This purges more pages, but the expensive part | ||
| 200 | * of purging is the TLB shootdowns, rather than the kernel state | ||
| 201 | * tracking; doing a little bit more of the latter is fine if it saves | ||
| 202 | * us from doing some of the former. | ||
| 203 | */ | ||
| 204 | |||
| 205 | /* | ||
| 206 | * The dirty pages are those that are touched but not active. Note that | ||
| 207 | * in a normal-ish case, HUGEPAGE_PAGES is something like 512 and the | ||
| 208 | * fb_group_t is 64 bits, so this is 64 bytes, spread across 8 | ||
| 209 | * fb_group_ts. | ||
| 210 | */ | ||
| 211 | fb_group_t dirty_pages[FB_NGROUPS(HUGEPAGE_PAGES)]; | ||
| 212 | fb_init(dirty_pages, HUGEPAGE_PAGES); | ||
| 213 | fb_bit_not(dirty_pages, hpdata->active_pages, HUGEPAGE_PAGES); | ||
| 214 | fb_bit_and(dirty_pages, dirty_pages, hpdata->touched_pages, | ||
| 215 | HUGEPAGE_PAGES); | ||
| 216 | |||
| 217 | fb_init(purge_state->to_purge, HUGEPAGE_PAGES); | ||
| 218 | size_t next_bit = 0; | ||
| 219 | while (next_bit < HUGEPAGE_PAGES) { | ||
| 220 | size_t next_dirty = fb_ffs(dirty_pages, HUGEPAGE_PAGES, | ||
| 221 | next_bit); | ||
| 222 | /* Recall that fb_ffs returns nbits if no set bit is found. */ | ||
| 223 | if (next_dirty == HUGEPAGE_PAGES) { | ||
| 224 | break; | ||
| 225 | } | ||
| 226 | size_t next_active = fb_ffs(hpdata->active_pages, | ||
| 227 | HUGEPAGE_PAGES, next_dirty); | ||
| 228 | /* | ||
| 229 | * Don't purge past the end of the dirty extent, into retained | ||
| 230 | * pages. This helps the kernel a tiny bit, but honestly it's | ||
| 231 | * mostly helpful for testing (where we tend to write test cases | ||
| 232 | * that think in terms of the dirty ranges). | ||
| 233 | */ | ||
| 234 | ssize_t last_dirty = fb_fls(dirty_pages, HUGEPAGE_PAGES, | ||
| 235 | next_active - 1); | ||
| 236 | assert(last_dirty >= 0); | ||
| 237 | assert((size_t)last_dirty >= next_dirty); | ||
| 238 | assert((size_t)last_dirty - next_dirty + 1 <= HUGEPAGE_PAGES); | ||
| 239 | |||
| 240 | fb_set_range(purge_state->to_purge, HUGEPAGE_PAGES, next_dirty, | ||
| 241 | last_dirty - next_dirty + 1); | ||
| 242 | next_bit = next_active + 1; | ||
| 243 | } | ||
| 244 | |||
| 245 | /* We should purge, at least, everything dirty. */ | ||
| 246 | size_t ndirty = hpdata->h_ntouched - hpdata->h_nactive; | ||
| 247 | purge_state->ndirty_to_purge = ndirty; | ||
| 248 | assert(ndirty <= fb_scount( | ||
| 249 | purge_state->to_purge, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)); | ||
| 250 | assert(ndirty == fb_scount(dirty_pages, HUGEPAGE_PAGES, 0, | ||
| 251 | HUGEPAGE_PAGES)); | ||
| 252 | |||
| 253 | hpdata_assert_consistent(hpdata); | ||
| 254 | |||
| 255 | return ndirty; | ||
| 256 | } | ||
| 257 | |||
| 258 | bool | ||
| 259 | hpdata_purge_next(hpdata_t *hpdata, hpdata_purge_state_t *purge_state, | ||
| 260 | void **r_purge_addr, size_t *r_purge_size) { | ||
| 261 | /* | ||
| 262 | * Note that we don't have a consistency check here; we're accessing | ||
| 263 | * hpdata without synchronization, and therefore have no right to expect | ||
| 264 | * a consistent state. | ||
| 265 | */ | ||
| 266 | assert(!hpdata_alloc_allowed_get(hpdata)); | ||
| 267 | |||
| 268 | if (purge_state->next_purge_search_begin == HUGEPAGE_PAGES) { | ||
| 269 | return false; | ||
| 270 | } | ||
| 271 | size_t purge_begin; | ||
| 272 | size_t purge_len; | ||
| 273 | bool found_range = fb_srange_iter(purge_state->to_purge, HUGEPAGE_PAGES, | ||
| 274 | purge_state->next_purge_search_begin, &purge_begin, &purge_len); | ||
| 275 | if (!found_range) { | ||
| 276 | return false; | ||
| 277 | } | ||
| 278 | |||
| 279 | *r_purge_addr = (void *)( | ||
| 280 | (uintptr_t)hpdata_addr_get(hpdata) + purge_begin * PAGE); | ||
| 281 | *r_purge_size = purge_len * PAGE; | ||
| 282 | |||
| 283 | purge_state->next_purge_search_begin = purge_begin + purge_len; | ||
| 284 | purge_state->npurged += purge_len; | ||
| 285 | assert(purge_state->npurged <= HUGEPAGE_PAGES); | ||
| 286 | |||
| 287 | return true; | ||
| 288 | } | ||
| 289 | |||
| 290 | void | ||
| 291 | hpdata_purge_end(hpdata_t *hpdata, hpdata_purge_state_t *purge_state) { | ||
| 292 | assert(!hpdata_alloc_allowed_get(hpdata)); | ||
| 293 | hpdata_assert_consistent(hpdata); | ||
| 294 | /* See the comment in reserve. */ | ||
| 295 | assert(!hpdata->h_in_psset || hpdata->h_updating); | ||
| 296 | |||
| 297 | assert(purge_state->npurged == fb_scount(purge_state->to_purge, | ||
| 298 | HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES)); | ||
| 299 | assert(purge_state->npurged >= purge_state->ndirty_to_purge); | ||
| 300 | |||
| 301 | fb_bit_not(purge_state->to_purge, purge_state->to_purge, | ||
| 302 | HUGEPAGE_PAGES); | ||
| 303 | fb_bit_and(hpdata->touched_pages, hpdata->touched_pages, | ||
| 304 | purge_state->to_purge, HUGEPAGE_PAGES); | ||
| 305 | assert(hpdata->h_ntouched >= purge_state->ndirty_to_purge); | ||
| 306 | hpdata->h_ntouched -= purge_state->ndirty_to_purge; | ||
| 307 | |||
| 308 | hpdata_assert_consistent(hpdata); | ||
| 309 | } | ||
| 310 | |||
| 311 | void | ||
| 312 | hpdata_hugify(hpdata_t *hpdata) { | ||
| 313 | hpdata_assert_consistent(hpdata); | ||
| 314 | hpdata->h_huge = true; | ||
| 315 | fb_set_range(hpdata->touched_pages, HUGEPAGE_PAGES, 0, HUGEPAGE_PAGES); | ||
| 316 | hpdata->h_ntouched = HUGEPAGE_PAGES; | ||
| 317 | hpdata_assert_consistent(hpdata); | ||
| 318 | } | ||
| 319 | |||
| 320 | void | ||
| 321 | hpdata_dehugify(hpdata_t *hpdata) { | ||
| 322 | hpdata_assert_consistent(hpdata); | ||
| 323 | hpdata->h_huge = false; | ||
| 324 | hpdata_assert_consistent(hpdata); | ||
| 325 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/inspect.c b/examples/redis-unstable/deps/jemalloc/src/inspect.c deleted file mode 100644 index 911b5d5..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/inspect.c +++ /dev/null | |||
| @@ -1,77 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | void | ||
| 5 | inspect_extent_util_stats_get(tsdn_t *tsdn, const void *ptr, size_t *nfree, | ||
| 6 | size_t *nregs, size_t *size) { | ||
| 7 | assert(ptr != NULL && nfree != NULL && nregs != NULL && size != NULL); | ||
| 8 | |||
| 9 | const edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 10 | if (unlikely(edata == NULL)) { | ||
| 11 | *nfree = *nregs = *size = 0; | ||
| 12 | return; | ||
| 13 | } | ||
| 14 | |||
| 15 | *size = edata_size_get(edata); | ||
| 16 | if (!edata_slab_get(edata)) { | ||
| 17 | *nfree = 0; | ||
| 18 | *nregs = 1; | ||
| 19 | } else { | ||
| 20 | *nfree = edata_nfree_get(edata); | ||
| 21 | *nregs = bin_infos[edata_szind_get(edata)].nregs; | ||
| 22 | assert(*nfree <= *nregs); | ||
| 23 | assert(*nfree * edata_usize_get(edata) <= *size); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | void | ||
| 28 | inspect_extent_util_stats_verbose_get(tsdn_t *tsdn, const void *ptr, | ||
| 29 | size_t *nfree, size_t *nregs, size_t *size, size_t *bin_nfree, | ||
| 30 | size_t *bin_nregs, void **slabcur_addr) { | ||
| 31 | assert(ptr != NULL && nfree != NULL && nregs != NULL && size != NULL | ||
| 32 | && bin_nfree != NULL && bin_nregs != NULL && slabcur_addr != NULL); | ||
| 33 | |||
| 34 | const edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 35 | if (unlikely(edata == NULL)) { | ||
| 36 | *nfree = *nregs = *size = *bin_nfree = *bin_nregs = 0; | ||
| 37 | *slabcur_addr = NULL; | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | |||
| 41 | *size = edata_size_get(edata); | ||
| 42 | if (!edata_slab_get(edata)) { | ||
| 43 | *nfree = *bin_nfree = *bin_nregs = 0; | ||
| 44 | *nregs = 1; | ||
| 45 | *slabcur_addr = NULL; | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | *nfree = edata_nfree_get(edata); | ||
| 50 | const szind_t szind = edata_szind_get(edata); | ||
| 51 | *nregs = bin_infos[szind].nregs; | ||
| 52 | assert(*nfree <= *nregs); | ||
| 53 | assert(*nfree * edata_usize_get(edata) <= *size); | ||
| 54 | |||
| 55 | arena_t *arena = (arena_t *)atomic_load_p( | ||
| 56 | &arenas[edata_arena_ind_get(edata)], ATOMIC_RELAXED); | ||
| 57 | assert(arena != NULL); | ||
| 58 | const unsigned binshard = edata_binshard_get(edata); | ||
| 59 | bin_t *bin = arena_get_bin(arena, szind, binshard); | ||
| 60 | |||
| 61 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 62 | if (config_stats) { | ||
| 63 | *bin_nregs = *nregs * bin->stats.curslabs; | ||
| 64 | assert(*bin_nregs >= bin->stats.curregs); | ||
| 65 | *bin_nfree = *bin_nregs - bin->stats.curregs; | ||
| 66 | } else { | ||
| 67 | *bin_nfree = *bin_nregs = 0; | ||
| 68 | } | ||
| 69 | edata_t *slab; | ||
| 70 | if (bin->slabcur != NULL) { | ||
| 71 | slab = bin->slabcur; | ||
| 72 | } else { | ||
| 73 | slab = edata_heap_first(&bin->slabs_nonfull); | ||
| 74 | } | ||
| 75 | *slabcur_addr = slab != NULL ? edata_addr_get(slab) : NULL; | ||
| 76 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 77 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/jemalloc.c b/examples/redis-unstable/deps/jemalloc/src/jemalloc.c deleted file mode 100644 index 9a115f8..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/jemalloc.c +++ /dev/null | |||
| @@ -1,4539 +0,0 @@ | |||
| 1 | #define JEMALLOC_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/atomic.h" | ||
| 7 | #include "jemalloc/internal/buf_writer.h" | ||
| 8 | #include "jemalloc/internal/ctl.h" | ||
| 9 | #include "jemalloc/internal/emap.h" | ||
| 10 | #include "jemalloc/internal/extent_dss.h" | ||
| 11 | #include "jemalloc/internal/extent_mmap.h" | ||
| 12 | #include "jemalloc/internal/fxp.h" | ||
| 13 | #include "jemalloc/internal/san.h" | ||
| 14 | #include "jemalloc/internal/hook.h" | ||
| 15 | #include "jemalloc/internal/jemalloc_internal_types.h" | ||
| 16 | #include "jemalloc/internal/log.h" | ||
| 17 | #include "jemalloc/internal/malloc_io.h" | ||
| 18 | #include "jemalloc/internal/mutex.h" | ||
| 19 | #include "jemalloc/internal/nstime.h" | ||
| 20 | #include "jemalloc/internal/rtree.h" | ||
| 21 | #include "jemalloc/internal/safety_check.h" | ||
| 22 | #include "jemalloc/internal/sc.h" | ||
| 23 | #include "jemalloc/internal/spin.h" | ||
| 24 | #include "jemalloc/internal/sz.h" | ||
| 25 | #include "jemalloc/internal/ticker.h" | ||
| 26 | #include "jemalloc/internal/thread_event.h" | ||
| 27 | #include "jemalloc/internal/util.h" | ||
| 28 | |||
| 29 | /******************************************************************************/ | ||
| 30 | /* Data. */ | ||
| 31 | |||
| 32 | /* Runtime configuration options. */ | ||
| 33 | const char *je_malloc_conf | ||
| 34 | #ifndef _WIN32 | ||
| 35 | JEMALLOC_ATTR(weak) | ||
| 36 | #endif | ||
| 37 | ; | ||
| 38 | /* | ||
| 39 | * The usual rule is that the closer to runtime you are, the higher priority | ||
| 40 | * your configuration settings are (so the jemalloc config options get lower | ||
| 41 | * priority than the per-binary setting, which gets lower priority than the /etc | ||
| 42 | * setting, which gets lower priority than the environment settings). | ||
| 43 | * | ||
| 44 | * But it's a fairly common use case in some testing environments for a user to | ||
| 45 | * be able to control the binary, but nothing else (e.g. a performancy canary | ||
| 46 | * uses the production OS and environment variables, but can run any binary in | ||
| 47 | * those circumstances). For these use cases, it's handy to have an in-binary | ||
| 48 | * mechanism for overriding environment variable settings, with the idea that if | ||
| 49 | * the results are positive they get promoted to the official settings, and | ||
| 50 | * moved from the binary to the environment variable. | ||
| 51 | * | ||
| 52 | * We don't actually want this to be widespread, so we'll give it a silly name | ||
| 53 | * and not mention it in headers or documentation. | ||
| 54 | */ | ||
| 55 | const char *je_malloc_conf_2_conf_harder | ||
| 56 | #ifndef _WIN32 | ||
| 57 | JEMALLOC_ATTR(weak) | ||
| 58 | #endif | ||
| 59 | ; | ||
| 60 | |||
| 61 | bool opt_abort = | ||
| 62 | #ifdef JEMALLOC_DEBUG | ||
| 63 | true | ||
| 64 | #else | ||
| 65 | false | ||
| 66 | #endif | ||
| 67 | ; | ||
| 68 | bool opt_abort_conf = | ||
| 69 | #ifdef JEMALLOC_DEBUG | ||
| 70 | true | ||
| 71 | #else | ||
| 72 | false | ||
| 73 | #endif | ||
| 74 | ; | ||
| 75 | /* Intentionally default off, even with debug builds. */ | ||
| 76 | bool opt_confirm_conf = false; | ||
| 77 | const char *opt_junk = | ||
| 78 | #if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL)) | ||
| 79 | "true" | ||
| 80 | #else | ||
| 81 | "false" | ||
| 82 | #endif | ||
| 83 | ; | ||
| 84 | bool opt_junk_alloc = | ||
| 85 | #if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL)) | ||
| 86 | true | ||
| 87 | #else | ||
| 88 | false | ||
| 89 | #endif | ||
| 90 | ; | ||
| 91 | bool opt_junk_free = | ||
| 92 | #if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL)) | ||
| 93 | true | ||
| 94 | #else | ||
| 95 | false | ||
| 96 | #endif | ||
| 97 | ; | ||
| 98 | bool opt_trust_madvise = | ||
| 99 | #ifdef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS | ||
| 100 | false | ||
| 101 | #else | ||
| 102 | true | ||
| 103 | #endif | ||
| 104 | ; | ||
| 105 | |||
| 106 | bool opt_cache_oblivious = | ||
| 107 | #ifdef JEMALLOC_CACHE_OBLIVIOUS | ||
| 108 | true | ||
| 109 | #else | ||
| 110 | false | ||
| 111 | #endif | ||
| 112 | ; | ||
| 113 | |||
| 114 | zero_realloc_action_t opt_zero_realloc_action = | ||
| 115 | #ifdef JEMALLOC_ZERO_REALLOC_DEFAULT_FREE | ||
| 116 | zero_realloc_action_free | ||
| 117 | #else | ||
| 118 | zero_realloc_action_alloc | ||
| 119 | #endif | ||
| 120 | ; | ||
| 121 | |||
| 122 | atomic_zu_t zero_realloc_count = ATOMIC_INIT(0); | ||
| 123 | |||
| 124 | const char *zero_realloc_mode_names[] = { | ||
| 125 | "alloc", | ||
| 126 | "free", | ||
| 127 | "abort", | ||
| 128 | }; | ||
| 129 | |||
| 130 | /* | ||
| 131 | * These are the documented values for junk fill debugging facilities -- see the | ||
| 132 | * man page. | ||
| 133 | */ | ||
| 134 | static const uint8_t junk_alloc_byte = 0xa5; | ||
| 135 | static const uint8_t junk_free_byte = 0x5a; | ||
| 136 | |||
| 137 | static void default_junk_alloc(void *ptr, size_t usize) { | ||
| 138 | memset(ptr, junk_alloc_byte, usize); | ||
| 139 | } | ||
| 140 | |||
| 141 | static void default_junk_free(void *ptr, size_t usize) { | ||
| 142 | memset(ptr, junk_free_byte, usize); | ||
| 143 | } | ||
| 144 | |||
| 145 | void (*junk_alloc_callback)(void *ptr, size_t size) = &default_junk_alloc; | ||
| 146 | void (*junk_free_callback)(void *ptr, size_t size) = &default_junk_free; | ||
| 147 | |||
| 148 | bool opt_utrace = false; | ||
| 149 | bool opt_xmalloc = false; | ||
| 150 | bool opt_experimental_infallible_new = false; | ||
| 151 | bool opt_zero = false; | ||
| 152 | unsigned opt_narenas = 0; | ||
| 153 | fxp_t opt_narenas_ratio = FXP_INIT_INT(4); | ||
| 154 | |||
| 155 | unsigned ncpus; | ||
| 156 | |||
| 157 | /* Protects arenas initialization. */ | ||
| 158 | malloc_mutex_t arenas_lock; | ||
| 159 | |||
| 160 | /* The global hpa, and whether it's on. */ | ||
| 161 | bool opt_hpa = false; | ||
| 162 | hpa_shard_opts_t opt_hpa_opts = HPA_SHARD_OPTS_DEFAULT; | ||
| 163 | sec_opts_t opt_hpa_sec_opts = SEC_OPTS_DEFAULT; | ||
| 164 | |||
| 165 | /* | ||
| 166 | * Arenas that are used to service external requests. Not all elements of the | ||
| 167 | * arenas array are necessarily used; arenas are created lazily as needed. | ||
| 168 | * | ||
| 169 | * arenas[0..narenas_auto) are used for automatic multiplexing of threads and | ||
| 170 | * arenas. arenas[narenas_auto..narenas_total) are only used if the application | ||
| 171 | * takes some action to create them and allocate from them. | ||
| 172 | * | ||
| 173 | * Points to an arena_t. | ||
| 174 | */ | ||
| 175 | JEMALLOC_ALIGNED(CACHELINE) | ||
| 176 | atomic_p_t arenas[MALLOCX_ARENA_LIMIT]; | ||
| 177 | static atomic_u_t narenas_total; /* Use narenas_total_*(). */ | ||
| 178 | /* Below three are read-only after initialization. */ | ||
| 179 | static arena_t *a0; /* arenas[0]. */ | ||
| 180 | unsigned narenas_auto; | ||
| 181 | unsigned manual_arena_base; | ||
| 182 | |||
| 183 | malloc_init_t malloc_init_state = malloc_init_uninitialized; | ||
| 184 | |||
| 185 | /* False should be the common case. Set to true to trigger initialization. */ | ||
| 186 | bool malloc_slow = true; | ||
| 187 | |||
| 188 | /* When malloc_slow is true, set the corresponding bits for sanity check. */ | ||
| 189 | enum { | ||
| 190 | flag_opt_junk_alloc = (1U), | ||
| 191 | flag_opt_junk_free = (1U << 1), | ||
| 192 | flag_opt_zero = (1U << 2), | ||
| 193 | flag_opt_utrace = (1U << 3), | ||
| 194 | flag_opt_xmalloc = (1U << 4) | ||
| 195 | }; | ||
| 196 | static uint8_t malloc_slow_flags; | ||
| 197 | |||
| 198 | #ifdef JEMALLOC_THREADED_INIT | ||
| 199 | /* Used to let the initializing thread recursively allocate. */ | ||
| 200 | # define NO_INITIALIZER ((unsigned long)0) | ||
| 201 | # define INITIALIZER pthread_self() | ||
| 202 | # define IS_INITIALIZER (malloc_initializer == pthread_self()) | ||
| 203 | static pthread_t malloc_initializer = NO_INITIALIZER; | ||
| 204 | #else | ||
| 205 | # define NO_INITIALIZER false | ||
| 206 | # define INITIALIZER true | ||
| 207 | # define IS_INITIALIZER malloc_initializer | ||
| 208 | static bool malloc_initializer = NO_INITIALIZER; | ||
| 209 | #endif | ||
| 210 | |||
| 211 | /* Used to avoid initialization races. */ | ||
| 212 | #ifdef _WIN32 | ||
| 213 | #if _WIN32_WINNT >= 0x0600 | ||
| 214 | static malloc_mutex_t init_lock = SRWLOCK_INIT; | ||
| 215 | #else | ||
| 216 | static malloc_mutex_t init_lock; | ||
| 217 | static bool init_lock_initialized = false; | ||
| 218 | |||
| 219 | JEMALLOC_ATTR(constructor) | ||
| 220 | static void WINAPI | ||
| 221 | _init_init_lock(void) { | ||
| 222 | /* | ||
| 223 | * If another constructor in the same binary is using mallctl to e.g. | ||
| 224 | * set up extent hooks, it may end up running before this one, and | ||
| 225 | * malloc_init_hard will crash trying to lock the uninitialized lock. So | ||
| 226 | * we force an initialization of the lock in malloc_init_hard as well. | ||
| 227 | * We don't try to care about atomicity of the accessed to the | ||
| 228 | * init_lock_initialized boolean, since it really only matters early in | ||
| 229 | * the process creation, before any separate thread normally starts | ||
| 230 | * doing anything. | ||
| 231 | */ | ||
| 232 | if (!init_lock_initialized) { | ||
| 233 | malloc_mutex_init(&init_lock, "init", WITNESS_RANK_INIT, | ||
| 234 | malloc_mutex_rank_exclusive); | ||
| 235 | } | ||
| 236 | init_lock_initialized = true; | ||
| 237 | } | ||
| 238 | |||
| 239 | #ifdef _MSC_VER | ||
| 240 | # pragma section(".CRT$XCU", read) | ||
| 241 | JEMALLOC_SECTION(".CRT$XCU") JEMALLOC_ATTR(used) | ||
| 242 | static const void (WINAPI *init_init_lock)(void) = _init_init_lock; | ||
| 243 | #endif | ||
| 244 | #endif | ||
| 245 | #else | ||
| 246 | static malloc_mutex_t init_lock = MALLOC_MUTEX_INITIALIZER; | ||
| 247 | #endif | ||
| 248 | |||
| 249 | typedef struct { | ||
| 250 | void *p; /* Input pointer (as in realloc(p, s)). */ | ||
| 251 | size_t s; /* Request size. */ | ||
| 252 | void *r; /* Result pointer. */ | ||
| 253 | } malloc_utrace_t; | ||
| 254 | |||
| 255 | #ifdef JEMALLOC_UTRACE | ||
| 256 | # define UTRACE(a, b, c) do { \ | ||
| 257 | if (unlikely(opt_utrace)) { \ | ||
| 258 | int utrace_serrno = errno; \ | ||
| 259 | malloc_utrace_t ut; \ | ||
| 260 | ut.p = (a); \ | ||
| 261 | ut.s = (b); \ | ||
| 262 | ut.r = (c); \ | ||
| 263 | UTRACE_CALL(&ut, sizeof(ut)); \ | ||
| 264 | errno = utrace_serrno; \ | ||
| 265 | } \ | ||
| 266 | } while (0) | ||
| 267 | #else | ||
| 268 | # define UTRACE(a, b, c) | ||
| 269 | #endif | ||
| 270 | |||
| 271 | /* Whether encountered any invalid config options. */ | ||
| 272 | static bool had_conf_error = false; | ||
| 273 | |||
| 274 | /******************************************************************************/ | ||
| 275 | /* | ||
| 276 | * Function prototypes for static functions that are referenced prior to | ||
| 277 | * definition. | ||
| 278 | */ | ||
| 279 | |||
| 280 | static bool malloc_init_hard_a0(void); | ||
| 281 | static bool malloc_init_hard(void); | ||
| 282 | |||
| 283 | /******************************************************************************/ | ||
| 284 | /* | ||
| 285 | * Begin miscellaneous support functions. | ||
| 286 | */ | ||
| 287 | |||
| 288 | JEMALLOC_ALWAYS_INLINE bool | ||
| 289 | malloc_init_a0(void) { | ||
| 290 | if (unlikely(malloc_init_state == malloc_init_uninitialized)) { | ||
| 291 | return malloc_init_hard_a0(); | ||
| 292 | } | ||
| 293 | return false; | ||
| 294 | } | ||
| 295 | |||
| 296 | JEMALLOC_ALWAYS_INLINE bool | ||
| 297 | malloc_init(void) { | ||
| 298 | if (unlikely(!malloc_initialized()) && malloc_init_hard()) { | ||
| 299 | return true; | ||
| 300 | } | ||
| 301 | return false; | ||
| 302 | } | ||
| 303 | |||
| 304 | /* | ||
| 305 | * The a0*() functions are used instead of i{d,}alloc() in situations that | ||
| 306 | * cannot tolerate TLS variable access. | ||
| 307 | */ | ||
| 308 | |||
| 309 | static void * | ||
| 310 | a0ialloc(size_t size, bool zero, bool is_internal) { | ||
| 311 | if (unlikely(malloc_init_a0())) { | ||
| 312 | return NULL; | ||
| 313 | } | ||
| 314 | |||
| 315 | return iallocztm(TSDN_NULL, size, sz_size2index(size), zero, NULL, | ||
| 316 | is_internal, arena_get(TSDN_NULL, 0, true), true); | ||
| 317 | } | ||
| 318 | |||
| 319 | static void | ||
| 320 | a0idalloc(void *ptr, bool is_internal) { | ||
| 321 | idalloctm(TSDN_NULL, ptr, NULL, NULL, is_internal, true); | ||
| 322 | } | ||
| 323 | |||
| 324 | void * | ||
| 325 | a0malloc(size_t size) { | ||
| 326 | return a0ialloc(size, false, true); | ||
| 327 | } | ||
| 328 | |||
| 329 | void | ||
| 330 | a0dalloc(void *ptr) { | ||
| 331 | a0idalloc(ptr, true); | ||
| 332 | } | ||
| 333 | |||
| 334 | /* | ||
| 335 | * FreeBSD's libc uses the bootstrap_*() functions in bootstrap-sensitive | ||
| 336 | * situations that cannot tolerate TLS variable access (TLS allocation and very | ||
| 337 | * early internal data structure initialization). | ||
| 338 | */ | ||
| 339 | |||
| 340 | void * | ||
| 341 | bootstrap_malloc(size_t size) { | ||
| 342 | if (unlikely(size == 0)) { | ||
| 343 | size = 1; | ||
| 344 | } | ||
| 345 | |||
| 346 | return a0ialloc(size, false, false); | ||
| 347 | } | ||
| 348 | |||
| 349 | void * | ||
| 350 | bootstrap_calloc(size_t num, size_t size) { | ||
| 351 | size_t num_size; | ||
| 352 | |||
| 353 | num_size = num * size; | ||
| 354 | if (unlikely(num_size == 0)) { | ||
| 355 | assert(num == 0 || size == 0); | ||
| 356 | num_size = 1; | ||
| 357 | } | ||
| 358 | |||
| 359 | return a0ialloc(num_size, true, false); | ||
| 360 | } | ||
| 361 | |||
| 362 | void | ||
| 363 | bootstrap_free(void *ptr) { | ||
| 364 | if (unlikely(ptr == NULL)) { | ||
| 365 | return; | ||
| 366 | } | ||
| 367 | |||
| 368 | a0idalloc(ptr, false); | ||
| 369 | } | ||
| 370 | |||
| 371 | void | ||
| 372 | arena_set(unsigned ind, arena_t *arena) { | ||
| 373 | atomic_store_p(&arenas[ind], arena, ATOMIC_RELEASE); | ||
| 374 | } | ||
| 375 | |||
| 376 | static void | ||
| 377 | narenas_total_set(unsigned narenas) { | ||
| 378 | atomic_store_u(&narenas_total, narenas, ATOMIC_RELEASE); | ||
| 379 | } | ||
| 380 | |||
| 381 | static void | ||
| 382 | narenas_total_inc(void) { | ||
| 383 | atomic_fetch_add_u(&narenas_total, 1, ATOMIC_RELEASE); | ||
| 384 | } | ||
| 385 | |||
| 386 | unsigned | ||
| 387 | narenas_total_get(void) { | ||
| 388 | return atomic_load_u(&narenas_total, ATOMIC_ACQUIRE); | ||
| 389 | } | ||
| 390 | |||
| 391 | /* Create a new arena and insert it into the arenas array at index ind. */ | ||
| 392 | static arena_t * | ||
| 393 | arena_init_locked(tsdn_t *tsdn, unsigned ind, const arena_config_t *config) { | ||
| 394 | arena_t *arena; | ||
| 395 | |||
| 396 | assert(ind <= narenas_total_get()); | ||
| 397 | if (ind >= MALLOCX_ARENA_LIMIT) { | ||
| 398 | return NULL; | ||
| 399 | } | ||
| 400 | if (ind == narenas_total_get()) { | ||
| 401 | narenas_total_inc(); | ||
| 402 | } | ||
| 403 | |||
| 404 | /* | ||
| 405 | * Another thread may have already initialized arenas[ind] if it's an | ||
| 406 | * auto arena. | ||
| 407 | */ | ||
| 408 | arena = arena_get(tsdn, ind, false); | ||
| 409 | if (arena != NULL) { | ||
| 410 | assert(arena_is_auto(arena)); | ||
| 411 | return arena; | ||
| 412 | } | ||
| 413 | |||
| 414 | /* Actually initialize the arena. */ | ||
| 415 | arena = arena_new(tsdn, ind, config); | ||
| 416 | |||
| 417 | return arena; | ||
| 418 | } | ||
| 419 | |||
| 420 | static void | ||
| 421 | arena_new_create_background_thread(tsdn_t *tsdn, unsigned ind) { | ||
| 422 | if (ind == 0) { | ||
| 423 | return; | ||
| 424 | } | ||
| 425 | /* | ||
| 426 | * Avoid creating a new background thread just for the huge arena, which | ||
| 427 | * purges eagerly by default. | ||
| 428 | */ | ||
| 429 | if (have_background_thread && !arena_is_huge(ind)) { | ||
| 430 | if (background_thread_create(tsdn_tsd(tsdn), ind)) { | ||
| 431 | malloc_printf("<jemalloc>: error in background thread " | ||
| 432 | "creation for arena %u. Abort.\n", ind); | ||
| 433 | abort(); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | arena_t * | ||
| 439 | arena_init(tsdn_t *tsdn, unsigned ind, const arena_config_t *config) { | ||
| 440 | arena_t *arena; | ||
| 441 | |||
| 442 | malloc_mutex_lock(tsdn, &arenas_lock); | ||
| 443 | arena = arena_init_locked(tsdn, ind, config); | ||
| 444 | malloc_mutex_unlock(tsdn, &arenas_lock); | ||
| 445 | |||
| 446 | arena_new_create_background_thread(tsdn, ind); | ||
| 447 | |||
| 448 | return arena; | ||
| 449 | } | ||
| 450 | |||
| 451 | static void | ||
| 452 | arena_bind(tsd_t *tsd, unsigned ind, bool internal) { | ||
| 453 | arena_t *arena = arena_get(tsd_tsdn(tsd), ind, false); | ||
| 454 | arena_nthreads_inc(arena, internal); | ||
| 455 | |||
| 456 | if (internal) { | ||
| 457 | tsd_iarena_set(tsd, arena); | ||
| 458 | } else { | ||
| 459 | tsd_arena_set(tsd, arena); | ||
| 460 | unsigned shard = atomic_fetch_add_u(&arena->binshard_next, 1, | ||
| 461 | ATOMIC_RELAXED); | ||
| 462 | tsd_binshards_t *bins = tsd_binshardsp_get(tsd); | ||
| 463 | for (unsigned i = 0; i < SC_NBINS; i++) { | ||
| 464 | assert(bin_infos[i].n_shards > 0 && | ||
| 465 | bin_infos[i].n_shards <= BIN_SHARDS_MAX); | ||
| 466 | bins->binshard[i] = shard % bin_infos[i].n_shards; | ||
| 467 | } | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | void | ||
| 472 | arena_migrate(tsd_t *tsd, arena_t *oldarena, arena_t *newarena) { | ||
| 473 | assert(oldarena != NULL); | ||
| 474 | assert(newarena != NULL); | ||
| 475 | |||
| 476 | arena_nthreads_dec(oldarena, false); | ||
| 477 | arena_nthreads_inc(newarena, false); | ||
| 478 | tsd_arena_set(tsd, newarena); | ||
| 479 | |||
| 480 | if (arena_nthreads_get(oldarena, false) == 0) { | ||
| 481 | /* Purge if the old arena has no associated threads anymore. */ | ||
| 482 | arena_decay(tsd_tsdn(tsd), oldarena, | ||
| 483 | /* is_background_thread */ false, /* all */ true); | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | static void | ||
| 488 | arena_unbind(tsd_t *tsd, unsigned ind, bool internal) { | ||
| 489 | arena_t *arena; | ||
| 490 | |||
| 491 | arena = arena_get(tsd_tsdn(tsd), ind, false); | ||
| 492 | arena_nthreads_dec(arena, internal); | ||
| 493 | |||
| 494 | if (internal) { | ||
| 495 | tsd_iarena_set(tsd, NULL); | ||
| 496 | } else { | ||
| 497 | tsd_arena_set(tsd, NULL); | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | /* Slow path, called only by arena_choose(). */ | ||
| 502 | arena_t * | ||
| 503 | arena_choose_hard(tsd_t *tsd, bool internal) { | ||
| 504 | arena_t *ret JEMALLOC_CC_SILENCE_INIT(NULL); | ||
| 505 | |||
| 506 | if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)) { | ||
| 507 | unsigned choose = percpu_arena_choose(); | ||
| 508 | ret = arena_get(tsd_tsdn(tsd), choose, true); | ||
| 509 | assert(ret != NULL); | ||
| 510 | arena_bind(tsd, arena_ind_get(ret), false); | ||
| 511 | arena_bind(tsd, arena_ind_get(ret), true); | ||
| 512 | |||
| 513 | return ret; | ||
| 514 | } | ||
| 515 | |||
| 516 | if (narenas_auto > 1) { | ||
| 517 | unsigned i, j, choose[2], first_null; | ||
| 518 | bool is_new_arena[2]; | ||
| 519 | |||
| 520 | /* | ||
| 521 | * Determine binding for both non-internal and internal | ||
| 522 | * allocation. | ||
| 523 | * | ||
| 524 | * choose[0]: For application allocation. | ||
| 525 | * choose[1]: For internal metadata allocation. | ||
| 526 | */ | ||
| 527 | |||
| 528 | for (j = 0; j < 2; j++) { | ||
| 529 | choose[j] = 0; | ||
| 530 | is_new_arena[j] = false; | ||
| 531 | } | ||
| 532 | |||
| 533 | first_null = narenas_auto; | ||
| 534 | malloc_mutex_lock(tsd_tsdn(tsd), &arenas_lock); | ||
| 535 | assert(arena_get(tsd_tsdn(tsd), 0, false) != NULL); | ||
| 536 | for (i = 1; i < narenas_auto; i++) { | ||
| 537 | if (arena_get(tsd_tsdn(tsd), i, false) != NULL) { | ||
| 538 | /* | ||
| 539 | * Choose the first arena that has the lowest | ||
| 540 | * number of threads assigned to it. | ||
| 541 | */ | ||
| 542 | for (j = 0; j < 2; j++) { | ||
| 543 | if (arena_nthreads_get(arena_get( | ||
| 544 | tsd_tsdn(tsd), i, false), !!j) < | ||
| 545 | arena_nthreads_get(arena_get( | ||
| 546 | tsd_tsdn(tsd), choose[j], false), | ||
| 547 | !!j)) { | ||
| 548 | choose[j] = i; | ||
| 549 | } | ||
| 550 | } | ||
| 551 | } else if (first_null == narenas_auto) { | ||
| 552 | /* | ||
| 553 | * Record the index of the first uninitialized | ||
| 554 | * arena, in case all extant arenas are in use. | ||
| 555 | * | ||
| 556 | * NB: It is possible for there to be | ||
| 557 | * discontinuities in terms of initialized | ||
| 558 | * versus uninitialized arenas, due to the | ||
| 559 | * "thread.arena" mallctl. | ||
| 560 | */ | ||
| 561 | first_null = i; | ||
| 562 | } | ||
| 563 | } | ||
| 564 | |||
| 565 | for (j = 0; j < 2; j++) { | ||
| 566 | if (arena_nthreads_get(arena_get(tsd_tsdn(tsd), | ||
| 567 | choose[j], false), !!j) == 0 || first_null == | ||
| 568 | narenas_auto) { | ||
| 569 | /* | ||
| 570 | * Use an unloaded arena, or the least loaded | ||
| 571 | * arena if all arenas are already initialized. | ||
| 572 | */ | ||
| 573 | if (!!j == internal) { | ||
| 574 | ret = arena_get(tsd_tsdn(tsd), | ||
| 575 | choose[j], false); | ||
| 576 | } | ||
| 577 | } else { | ||
| 578 | arena_t *arena; | ||
| 579 | |||
| 580 | /* Initialize a new arena. */ | ||
| 581 | choose[j] = first_null; | ||
| 582 | arena = arena_init_locked(tsd_tsdn(tsd), | ||
| 583 | choose[j], &arena_config_default); | ||
| 584 | if (arena == NULL) { | ||
| 585 | malloc_mutex_unlock(tsd_tsdn(tsd), | ||
| 586 | &arenas_lock); | ||
| 587 | return NULL; | ||
| 588 | } | ||
| 589 | is_new_arena[j] = true; | ||
| 590 | if (!!j == internal) { | ||
| 591 | ret = arena; | ||
| 592 | } | ||
| 593 | } | ||
| 594 | arena_bind(tsd, choose[j], !!j); | ||
| 595 | } | ||
| 596 | malloc_mutex_unlock(tsd_tsdn(tsd), &arenas_lock); | ||
| 597 | |||
| 598 | for (j = 0; j < 2; j++) { | ||
| 599 | if (is_new_arena[j]) { | ||
| 600 | assert(choose[j] > 0); | ||
| 601 | arena_new_create_background_thread( | ||
| 602 | tsd_tsdn(tsd), choose[j]); | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | } else { | ||
| 607 | ret = arena_get(tsd_tsdn(tsd), 0, false); | ||
| 608 | arena_bind(tsd, 0, false); | ||
| 609 | arena_bind(tsd, 0, true); | ||
| 610 | } | ||
| 611 | |||
| 612 | return ret; | ||
| 613 | } | ||
| 614 | |||
| 615 | void | ||
| 616 | iarena_cleanup(tsd_t *tsd) { | ||
| 617 | arena_t *iarena; | ||
| 618 | |||
| 619 | iarena = tsd_iarena_get(tsd); | ||
| 620 | if (iarena != NULL) { | ||
| 621 | arena_unbind(tsd, arena_ind_get(iarena), true); | ||
| 622 | } | ||
| 623 | } | ||
| 624 | |||
| 625 | void | ||
| 626 | arena_cleanup(tsd_t *tsd) { | ||
| 627 | arena_t *arena; | ||
| 628 | |||
| 629 | arena = tsd_arena_get(tsd); | ||
| 630 | if (arena != NULL) { | ||
| 631 | arena_unbind(tsd, arena_ind_get(arena), false); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 635 | static void | ||
| 636 | stats_print_atexit(void) { | ||
| 637 | if (config_stats) { | ||
| 638 | tsdn_t *tsdn; | ||
| 639 | unsigned narenas, i; | ||
| 640 | |||
| 641 | tsdn = tsdn_fetch(); | ||
| 642 | |||
| 643 | /* | ||
| 644 | * Merge stats from extant threads. This is racy, since | ||
| 645 | * individual threads do not lock when recording tcache stats | ||
| 646 | * events. As a consequence, the final stats may be slightly | ||
| 647 | * out of date by the time they are reported, if other threads | ||
| 648 | * continue to allocate. | ||
| 649 | */ | ||
| 650 | for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { | ||
| 651 | arena_t *arena = arena_get(tsdn, i, false); | ||
| 652 | if (arena != NULL) { | ||
| 653 | tcache_slow_t *tcache_slow; | ||
| 654 | |||
| 655 | malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); | ||
| 656 | ql_foreach(tcache_slow, &arena->tcache_ql, | ||
| 657 | link) { | ||
| 658 | tcache_stats_merge(tsdn, | ||
| 659 | tcache_slow->tcache, arena); | ||
| 660 | } | ||
| 661 | malloc_mutex_unlock(tsdn, | ||
| 662 | &arena->tcache_ql_mtx); | ||
| 663 | } | ||
| 664 | } | ||
| 665 | } | ||
| 666 | je_malloc_stats_print(NULL, NULL, opt_stats_print_opts); | ||
| 667 | } | ||
| 668 | |||
| 669 | /* | ||
| 670 | * Ensure that we don't hold any locks upon entry to or exit from allocator | ||
| 671 | * code (in a "broad" sense that doesn't count a reentrant allocation as an | ||
| 672 | * entrance or exit). | ||
| 673 | */ | ||
| 674 | JEMALLOC_ALWAYS_INLINE void | ||
| 675 | check_entry_exit_locking(tsdn_t *tsdn) { | ||
| 676 | if (!config_debug) { | ||
| 677 | return; | ||
| 678 | } | ||
| 679 | if (tsdn_null(tsdn)) { | ||
| 680 | return; | ||
| 681 | } | ||
| 682 | tsd_t *tsd = tsdn_tsd(tsdn); | ||
| 683 | /* | ||
| 684 | * It's possible we hold locks at entry/exit if we're in a nested | ||
| 685 | * allocation. | ||
| 686 | */ | ||
| 687 | int8_t reentrancy_level = tsd_reentrancy_level_get(tsd); | ||
| 688 | if (reentrancy_level != 0) { | ||
| 689 | return; | ||
| 690 | } | ||
| 691 | witness_assert_lockless(tsdn_witness_tsdp_get(tsdn)); | ||
| 692 | } | ||
| 693 | |||
| 694 | /* | ||
| 695 | * End miscellaneous support functions. | ||
| 696 | */ | ||
| 697 | /******************************************************************************/ | ||
| 698 | /* | ||
| 699 | * Begin initialization functions. | ||
| 700 | */ | ||
| 701 | |||
| 702 | static char * | ||
| 703 | jemalloc_secure_getenv(const char *name) { | ||
| 704 | #ifdef JEMALLOC_HAVE_SECURE_GETENV | ||
| 705 | return secure_getenv(name); | ||
| 706 | #else | ||
| 707 | # ifdef JEMALLOC_HAVE_ISSETUGID | ||
| 708 | if (issetugid() != 0) { | ||
| 709 | return NULL; | ||
| 710 | } | ||
| 711 | # endif | ||
| 712 | return getenv(name); | ||
| 713 | #endif | ||
| 714 | } | ||
| 715 | |||
| 716 | static unsigned | ||
| 717 | malloc_ncpus(void) { | ||
| 718 | long result; | ||
| 719 | |||
| 720 | #ifdef _WIN32 | ||
| 721 | SYSTEM_INFO si; | ||
| 722 | GetSystemInfo(&si); | ||
| 723 | result = si.dwNumberOfProcessors; | ||
| 724 | #elif defined(CPU_COUNT) | ||
| 725 | /* | ||
| 726 | * glibc >= 2.6 has the CPU_COUNT macro. | ||
| 727 | * | ||
| 728 | * glibc's sysconf() uses isspace(). glibc allocates for the first time | ||
| 729 | * *before* setting up the isspace tables. Therefore we need a | ||
| 730 | * different method to get the number of CPUs. | ||
| 731 | * | ||
| 732 | * The getaffinity approach is also preferred when only a subset of CPUs | ||
| 733 | * is available, to avoid using more arenas than necessary. | ||
| 734 | */ | ||
| 735 | { | ||
| 736 | # if defined(__FreeBSD__) || defined(__DragonFly__) | ||
| 737 | cpuset_t set; | ||
| 738 | # else | ||
| 739 | cpu_set_t set; | ||
| 740 | # endif | ||
| 741 | # if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) | ||
| 742 | sched_getaffinity(0, sizeof(set), &set); | ||
| 743 | # else | ||
| 744 | pthread_getaffinity_np(pthread_self(), sizeof(set), &set); | ||
| 745 | # endif | ||
| 746 | result = CPU_COUNT(&set); | ||
| 747 | } | ||
| 748 | #else | ||
| 749 | result = sysconf(_SC_NPROCESSORS_ONLN); | ||
| 750 | #endif | ||
| 751 | return ((result == -1) ? 1 : (unsigned)result); | ||
| 752 | } | ||
| 753 | |||
| 754 | /* | ||
| 755 | * Ensure that number of CPUs is determistinc, i.e. it is the same based on: | ||
| 756 | * - sched_getaffinity() | ||
| 757 | * - _SC_NPROCESSORS_ONLN | ||
| 758 | * - _SC_NPROCESSORS_CONF | ||
| 759 | * Since otherwise tricky things is possible with percpu arenas in use. | ||
| 760 | */ | ||
| 761 | static bool | ||
| 762 | malloc_cpu_count_is_deterministic() | ||
| 763 | { | ||
| 764 | #ifdef _WIN32 | ||
| 765 | return true; | ||
| 766 | #else | ||
| 767 | long cpu_onln = sysconf(_SC_NPROCESSORS_ONLN); | ||
| 768 | long cpu_conf = sysconf(_SC_NPROCESSORS_CONF); | ||
| 769 | if (cpu_onln != cpu_conf) { | ||
| 770 | return false; | ||
| 771 | } | ||
| 772 | # if defined(CPU_COUNT) | ||
| 773 | # if defined(__FreeBSD__) || defined(__DragonFly__) | ||
| 774 | cpuset_t set; | ||
| 775 | # else | ||
| 776 | cpu_set_t set; | ||
| 777 | # endif /* __FreeBSD__ */ | ||
| 778 | # if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY) | ||
| 779 | sched_getaffinity(0, sizeof(set), &set); | ||
| 780 | # else /* !JEMALLOC_HAVE_SCHED_SETAFFINITY */ | ||
| 781 | pthread_getaffinity_np(pthread_self(), sizeof(set), &set); | ||
| 782 | # endif /* JEMALLOC_HAVE_SCHED_SETAFFINITY */ | ||
| 783 | long cpu_affinity = CPU_COUNT(&set); | ||
| 784 | if (cpu_affinity != cpu_conf) { | ||
| 785 | return false; | ||
| 786 | } | ||
| 787 | # endif /* CPU_COUNT */ | ||
| 788 | return true; | ||
| 789 | #endif | ||
| 790 | } | ||
| 791 | |||
| 792 | static void | ||
| 793 | init_opt_stats_opts(const char *v, size_t vlen, char *dest) { | ||
| 794 | size_t opts_len = strlen(dest); | ||
| 795 | assert(opts_len <= stats_print_tot_num_options); | ||
| 796 | |||
| 797 | for (size_t i = 0; i < vlen; i++) { | ||
| 798 | switch (v[i]) { | ||
| 799 | #define OPTION(o, v, d, s) case o: break; | ||
| 800 | STATS_PRINT_OPTIONS | ||
| 801 | #undef OPTION | ||
| 802 | default: continue; | ||
| 803 | } | ||
| 804 | |||
| 805 | if (strchr(dest, v[i]) != NULL) { | ||
| 806 | /* Ignore repeated. */ | ||
| 807 | continue; | ||
| 808 | } | ||
| 809 | |||
| 810 | dest[opts_len++] = v[i]; | ||
| 811 | dest[opts_len] = '\0'; | ||
| 812 | assert(opts_len <= stats_print_tot_num_options); | ||
| 813 | } | ||
| 814 | assert(opts_len == strlen(dest)); | ||
| 815 | } | ||
| 816 | |||
| 817 | /* Reads the next size pair in a multi-sized option. */ | ||
| 818 | static bool | ||
| 819 | malloc_conf_multi_sizes_next(const char **slab_size_segment_cur, | ||
| 820 | size_t *vlen_left, size_t *slab_start, size_t *slab_end, size_t *new_size) { | ||
| 821 | const char *cur = *slab_size_segment_cur; | ||
| 822 | char *end; | ||
| 823 | uintmax_t um; | ||
| 824 | |||
| 825 | set_errno(0); | ||
| 826 | |||
| 827 | /* First number, then '-' */ | ||
| 828 | um = malloc_strtoumax(cur, &end, 0); | ||
| 829 | if (get_errno() != 0 || *end != '-') { | ||
| 830 | return true; | ||
| 831 | } | ||
| 832 | *slab_start = (size_t)um; | ||
| 833 | cur = end + 1; | ||
| 834 | |||
| 835 | /* Second number, then ':' */ | ||
| 836 | um = malloc_strtoumax(cur, &end, 0); | ||
| 837 | if (get_errno() != 0 || *end != ':') { | ||
| 838 | return true; | ||
| 839 | } | ||
| 840 | *slab_end = (size_t)um; | ||
| 841 | cur = end + 1; | ||
| 842 | |||
| 843 | /* Last number */ | ||
| 844 | um = malloc_strtoumax(cur, &end, 0); | ||
| 845 | if (get_errno() != 0) { | ||
| 846 | return true; | ||
| 847 | } | ||
| 848 | *new_size = (size_t)um; | ||
| 849 | |||
| 850 | /* Consume the separator if there is one. */ | ||
| 851 | if (*end == '|') { | ||
| 852 | end++; | ||
| 853 | } | ||
| 854 | |||
| 855 | *vlen_left -= end - *slab_size_segment_cur; | ||
| 856 | *slab_size_segment_cur = end; | ||
| 857 | |||
| 858 | return false; | ||
| 859 | } | ||
| 860 | |||
| 861 | static bool | ||
| 862 | malloc_conf_next(char const **opts_p, char const **k_p, size_t *klen_p, | ||
| 863 | char const **v_p, size_t *vlen_p) { | ||
| 864 | bool accept; | ||
| 865 | const char *opts = *opts_p; | ||
| 866 | |||
| 867 | *k_p = opts; | ||
| 868 | |||
| 869 | for (accept = false; !accept;) { | ||
| 870 | switch (*opts) { | ||
| 871 | case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': | ||
| 872 | case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': | ||
| 873 | case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': | ||
| 874 | case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': | ||
| 875 | case 'Y': case 'Z': | ||
| 876 | case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': | ||
| 877 | case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': | ||
| 878 | case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': | ||
| 879 | case 's': case 't': case 'u': case 'v': case 'w': case 'x': | ||
| 880 | case 'y': case 'z': | ||
| 881 | case '0': case '1': case '2': case '3': case '4': case '5': | ||
| 882 | case '6': case '7': case '8': case '9': | ||
| 883 | case '_': | ||
| 884 | opts++; | ||
| 885 | break; | ||
| 886 | case ':': | ||
| 887 | opts++; | ||
| 888 | *klen_p = (uintptr_t)opts - 1 - (uintptr_t)*k_p; | ||
| 889 | *v_p = opts; | ||
| 890 | accept = true; | ||
| 891 | break; | ||
| 892 | case '\0': | ||
| 893 | if (opts != *opts_p) { | ||
| 894 | malloc_write("<jemalloc>: Conf string ends " | ||
| 895 | "with key\n"); | ||
| 896 | had_conf_error = true; | ||
| 897 | } | ||
| 898 | return true; | ||
| 899 | default: | ||
| 900 | malloc_write("<jemalloc>: Malformed conf string\n"); | ||
| 901 | had_conf_error = true; | ||
| 902 | return true; | ||
| 903 | } | ||
| 904 | } | ||
| 905 | |||
| 906 | for (accept = false; !accept;) { | ||
| 907 | switch (*opts) { | ||
| 908 | case ',': | ||
| 909 | opts++; | ||
| 910 | /* | ||
| 911 | * Look ahead one character here, because the next time | ||
| 912 | * this function is called, it will assume that end of | ||
| 913 | * input has been cleanly reached if no input remains, | ||
| 914 | * but we have optimistically already consumed the | ||
| 915 | * comma if one exists. | ||
| 916 | */ | ||
| 917 | if (*opts == '\0') { | ||
| 918 | malloc_write("<jemalloc>: Conf string ends " | ||
| 919 | "with comma\n"); | ||
| 920 | had_conf_error = true; | ||
| 921 | } | ||
| 922 | *vlen_p = (uintptr_t)opts - 1 - (uintptr_t)*v_p; | ||
| 923 | accept = true; | ||
| 924 | break; | ||
| 925 | case '\0': | ||
| 926 | *vlen_p = (uintptr_t)opts - (uintptr_t)*v_p; | ||
| 927 | accept = true; | ||
| 928 | break; | ||
| 929 | default: | ||
| 930 | opts++; | ||
| 931 | break; | ||
| 932 | } | ||
| 933 | } | ||
| 934 | |||
| 935 | *opts_p = opts; | ||
| 936 | return false; | ||
| 937 | } | ||
| 938 | |||
| 939 | static void | ||
| 940 | malloc_abort_invalid_conf(void) { | ||
| 941 | assert(opt_abort_conf); | ||
| 942 | malloc_printf("<jemalloc>: Abort (abort_conf:true) on invalid conf " | ||
| 943 | "value (see above).\n"); | ||
| 944 | abort(); | ||
| 945 | } | ||
| 946 | |||
| 947 | static void | ||
| 948 | malloc_conf_error(const char *msg, const char *k, size_t klen, const char *v, | ||
| 949 | size_t vlen) { | ||
| 950 | malloc_printf("<jemalloc>: %s: %.*s:%.*s\n", msg, (int)klen, k, | ||
| 951 | (int)vlen, v); | ||
| 952 | /* If abort_conf is set, error out after processing all options. */ | ||
| 953 | const char *experimental = "experimental_"; | ||
| 954 | if (strncmp(k, experimental, strlen(experimental)) == 0) { | ||
| 955 | /* However, tolerate experimental features. */ | ||
| 956 | return; | ||
| 957 | } | ||
| 958 | had_conf_error = true; | ||
| 959 | } | ||
| 960 | |||
| 961 | static void | ||
| 962 | malloc_slow_flag_init(void) { | ||
| 963 | /* | ||
| 964 | * Combine the runtime options into malloc_slow for fast path. Called | ||
| 965 | * after processing all the options. | ||
| 966 | */ | ||
| 967 | malloc_slow_flags |= (opt_junk_alloc ? flag_opt_junk_alloc : 0) | ||
| 968 | | (opt_junk_free ? flag_opt_junk_free : 0) | ||
| 969 | | (opt_zero ? flag_opt_zero : 0) | ||
| 970 | | (opt_utrace ? flag_opt_utrace : 0) | ||
| 971 | | (opt_xmalloc ? flag_opt_xmalloc : 0); | ||
| 972 | |||
| 973 | malloc_slow = (malloc_slow_flags != 0); | ||
| 974 | } | ||
| 975 | |||
| 976 | /* Number of sources for initializing malloc_conf */ | ||
| 977 | #define MALLOC_CONF_NSOURCES 5 | ||
| 978 | |||
| 979 | static const char * | ||
| 980 | obtain_malloc_conf(unsigned which_source, char buf[PATH_MAX + 1]) { | ||
| 981 | if (config_debug) { | ||
| 982 | static unsigned read_source = 0; | ||
| 983 | /* | ||
| 984 | * Each source should only be read once, to minimize # of | ||
| 985 | * syscalls on init. | ||
| 986 | */ | ||
| 987 | assert(read_source++ == which_source); | ||
| 988 | } | ||
| 989 | assert(which_source < MALLOC_CONF_NSOURCES); | ||
| 990 | |||
| 991 | const char *ret; | ||
| 992 | switch (which_source) { | ||
| 993 | case 0: | ||
| 994 | ret = config_malloc_conf; | ||
| 995 | break; | ||
| 996 | case 1: | ||
| 997 | if (je_malloc_conf != NULL) { | ||
| 998 | /* Use options that were compiled into the program. */ | ||
| 999 | ret = je_malloc_conf; | ||
| 1000 | } else { | ||
| 1001 | /* No configuration specified. */ | ||
| 1002 | ret = NULL; | ||
| 1003 | } | ||
| 1004 | break; | ||
| 1005 | case 2: { | ||
| 1006 | ssize_t linklen = 0; | ||
| 1007 | #ifndef _WIN32 | ||
| 1008 | int saved_errno = errno; | ||
| 1009 | const char *linkname = | ||
| 1010 | # ifdef JEMALLOC_PREFIX | ||
| 1011 | "/etc/"JEMALLOC_PREFIX"malloc.conf" | ||
| 1012 | # else | ||
| 1013 | "/etc/malloc.conf" | ||
| 1014 | # endif | ||
| 1015 | ; | ||
| 1016 | |||
| 1017 | /* | ||
| 1018 | * Try to use the contents of the "/etc/malloc.conf" symbolic | ||
| 1019 | * link's name. | ||
| 1020 | */ | ||
| 1021 | #ifndef JEMALLOC_READLINKAT | ||
| 1022 | linklen = readlink(linkname, buf, PATH_MAX); | ||
| 1023 | #else | ||
| 1024 | linklen = readlinkat(AT_FDCWD, linkname, buf, PATH_MAX); | ||
| 1025 | #endif | ||
| 1026 | if (linklen == -1) { | ||
| 1027 | /* No configuration specified. */ | ||
| 1028 | linklen = 0; | ||
| 1029 | /* Restore errno. */ | ||
| 1030 | set_errno(saved_errno); | ||
| 1031 | } | ||
| 1032 | #endif | ||
| 1033 | buf[linklen] = '\0'; | ||
| 1034 | ret = buf; | ||
| 1035 | break; | ||
| 1036 | } case 3: { | ||
| 1037 | const char *envname = | ||
| 1038 | #ifdef JEMALLOC_PREFIX | ||
| 1039 | JEMALLOC_CPREFIX"MALLOC_CONF" | ||
| 1040 | #else | ||
| 1041 | "MALLOC_CONF" | ||
| 1042 | #endif | ||
| 1043 | ; | ||
| 1044 | |||
| 1045 | if ((ret = jemalloc_secure_getenv(envname)) != NULL) { | ||
| 1046 | /* | ||
| 1047 | * Do nothing; opts is already initialized to the value | ||
| 1048 | * of the MALLOC_CONF environment variable. | ||
| 1049 | */ | ||
| 1050 | } else { | ||
| 1051 | /* No configuration specified. */ | ||
| 1052 | ret = NULL; | ||
| 1053 | } | ||
| 1054 | break; | ||
| 1055 | } case 4: { | ||
| 1056 | ret = je_malloc_conf_2_conf_harder; | ||
| 1057 | break; | ||
| 1058 | } default: | ||
| 1059 | not_reached(); | ||
| 1060 | ret = NULL; | ||
| 1061 | } | ||
| 1062 | return ret; | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | static void | ||
| 1066 | malloc_conf_init_helper(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS], | ||
| 1067 | bool initial_call, const char *opts_cache[MALLOC_CONF_NSOURCES], | ||
| 1068 | char buf[PATH_MAX + 1]) { | ||
| 1069 | static const char *opts_explain[MALLOC_CONF_NSOURCES] = { | ||
| 1070 | "string specified via --with-malloc-conf", | ||
| 1071 | "string pointed to by the global variable malloc_conf", | ||
| 1072 | ("\"name\" of the file referenced by the symbolic link named " | ||
| 1073 | "/etc/malloc.conf"), | ||
| 1074 | "value of the environment variable MALLOC_CONF", | ||
| 1075 | ("string pointed to by the global variable " | ||
| 1076 | "malloc_conf_2_conf_harder"), | ||
| 1077 | }; | ||
| 1078 | unsigned i; | ||
| 1079 | const char *opts, *k, *v; | ||
| 1080 | size_t klen, vlen; | ||
| 1081 | |||
| 1082 | for (i = 0; i < MALLOC_CONF_NSOURCES; i++) { | ||
| 1083 | /* Get runtime configuration. */ | ||
| 1084 | if (initial_call) { | ||
| 1085 | opts_cache[i] = obtain_malloc_conf(i, buf); | ||
| 1086 | } | ||
| 1087 | opts = opts_cache[i]; | ||
| 1088 | if (!initial_call && opt_confirm_conf) { | ||
| 1089 | malloc_printf( | ||
| 1090 | "<jemalloc>: malloc_conf #%u (%s): \"%s\"\n", | ||
| 1091 | i + 1, opts_explain[i], opts != NULL ? opts : ""); | ||
| 1092 | } | ||
| 1093 | if (opts == NULL) { | ||
| 1094 | continue; | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | while (*opts != '\0' && !malloc_conf_next(&opts, &k, &klen, &v, | ||
| 1098 | &vlen)) { | ||
| 1099 | |||
| 1100 | #define CONF_ERROR(msg, k, klen, v, vlen) \ | ||
| 1101 | if (!initial_call) { \ | ||
| 1102 | malloc_conf_error( \ | ||
| 1103 | msg, k, klen, v, vlen); \ | ||
| 1104 | cur_opt_valid = false; \ | ||
| 1105 | } | ||
| 1106 | #define CONF_CONTINUE { \ | ||
| 1107 | if (!initial_call && opt_confirm_conf \ | ||
| 1108 | && cur_opt_valid) { \ | ||
| 1109 | malloc_printf("<jemalloc>: -- " \ | ||
| 1110 | "Set conf value: %.*s:%.*s" \ | ||
| 1111 | "\n", (int)klen, k, \ | ||
| 1112 | (int)vlen, v); \ | ||
| 1113 | } \ | ||
| 1114 | continue; \ | ||
| 1115 | } | ||
| 1116 | #define CONF_MATCH(n) \ | ||
| 1117 | (sizeof(n)-1 == klen && strncmp(n, k, klen) == 0) | ||
| 1118 | #define CONF_MATCH_VALUE(n) \ | ||
| 1119 | (sizeof(n)-1 == vlen && strncmp(n, v, vlen) == 0) | ||
| 1120 | #define CONF_HANDLE_BOOL(o, n) \ | ||
| 1121 | if (CONF_MATCH(n)) { \ | ||
| 1122 | if (CONF_MATCH_VALUE("true")) { \ | ||
| 1123 | o = true; \ | ||
| 1124 | } else if (CONF_MATCH_VALUE("false")) { \ | ||
| 1125 | o = false; \ | ||
| 1126 | } else { \ | ||
| 1127 | CONF_ERROR("Invalid conf value",\ | ||
| 1128 | k, klen, v, vlen); \ | ||
| 1129 | } \ | ||
| 1130 | CONF_CONTINUE; \ | ||
| 1131 | } | ||
| 1132 | /* | ||
| 1133 | * One of the CONF_MIN macros below expands, in one of the use points, | ||
| 1134 | * to "unsigned integer < 0", which is always false, triggering the | ||
| 1135 | * GCC -Wtype-limits warning, which we disable here and re-enable below. | ||
| 1136 | */ | ||
| 1137 | JEMALLOC_DIAGNOSTIC_PUSH | ||
| 1138 | JEMALLOC_DIAGNOSTIC_IGNORE_TYPE_LIMITS | ||
| 1139 | |||
| 1140 | #define CONF_DONT_CHECK_MIN(um, min) false | ||
| 1141 | #define CONF_CHECK_MIN(um, min) ((um) < (min)) | ||
| 1142 | #define CONF_DONT_CHECK_MAX(um, max) false | ||
| 1143 | #define CONF_CHECK_MAX(um, max) ((um) > (max)) | ||
| 1144 | |||
| 1145 | #define CONF_VALUE_READ(max_t, result) \ | ||
| 1146 | char *end; \ | ||
| 1147 | set_errno(0); \ | ||
| 1148 | result = (max_t)malloc_strtoumax(v, &end, 0); | ||
| 1149 | #define CONF_VALUE_READ_FAIL() \ | ||
| 1150 | (get_errno() != 0 || (uintptr_t)end - (uintptr_t)v != vlen) | ||
| 1151 | |||
| 1152 | #define CONF_HANDLE_T(t, max_t, o, n, min, max, check_min, check_max, clip) \ | ||
| 1153 | if (CONF_MATCH(n)) { \ | ||
| 1154 | max_t mv; \ | ||
| 1155 | CONF_VALUE_READ(max_t, mv) \ | ||
| 1156 | if (CONF_VALUE_READ_FAIL()) { \ | ||
| 1157 | CONF_ERROR("Invalid conf value",\ | ||
| 1158 | k, klen, v, vlen); \ | ||
| 1159 | } else if (clip) { \ | ||
| 1160 | if (check_min(mv, (t)(min))) { \ | ||
| 1161 | o = (t)(min); \ | ||
| 1162 | } else if ( \ | ||
| 1163 | check_max(mv, (t)(max))) { \ | ||
| 1164 | o = (t)(max); \ | ||
| 1165 | } else { \ | ||
| 1166 | o = (t)mv; \ | ||
| 1167 | } \ | ||
| 1168 | } else { \ | ||
| 1169 | if (check_min(mv, (t)(min)) || \ | ||
| 1170 | check_max(mv, (t)(max))) { \ | ||
| 1171 | CONF_ERROR( \ | ||
| 1172 | "Out-of-range " \ | ||
| 1173 | "conf value", \ | ||
| 1174 | k, klen, v, vlen); \ | ||
| 1175 | } else { \ | ||
| 1176 | o = (t)mv; \ | ||
| 1177 | } \ | ||
| 1178 | } \ | ||
| 1179 | CONF_CONTINUE; \ | ||
| 1180 | } | ||
| 1181 | #define CONF_HANDLE_T_U(t, o, n, min, max, check_min, check_max, clip) \ | ||
| 1182 | CONF_HANDLE_T(t, uintmax_t, o, n, min, max, check_min, \ | ||
| 1183 | check_max, clip) | ||
| 1184 | #define CONF_HANDLE_T_SIGNED(t, o, n, min, max, check_min, check_max, clip)\ | ||
| 1185 | CONF_HANDLE_T(t, intmax_t, o, n, min, max, check_min, \ | ||
| 1186 | check_max, clip) | ||
| 1187 | |||
| 1188 | #define CONF_HANDLE_UNSIGNED(o, n, min, max, check_min, check_max, \ | ||
| 1189 | clip) \ | ||
| 1190 | CONF_HANDLE_T_U(unsigned, o, n, min, max, \ | ||
| 1191 | check_min, check_max, clip) | ||
| 1192 | #define CONF_HANDLE_SIZE_T(o, n, min, max, check_min, check_max, clip) \ | ||
| 1193 | CONF_HANDLE_T_U(size_t, o, n, min, max, \ | ||
| 1194 | check_min, check_max, clip) | ||
| 1195 | #define CONF_HANDLE_INT64_T(o, n, min, max, check_min, check_max, clip) \ | ||
| 1196 | CONF_HANDLE_T_SIGNED(int64_t, o, n, min, max, \ | ||
| 1197 | check_min, check_max, clip) | ||
| 1198 | #define CONF_HANDLE_UINT64_T(o, n, min, max, check_min, check_max, clip)\ | ||
| 1199 | CONF_HANDLE_T_U(uint64_t, o, n, min, max, \ | ||
| 1200 | check_min, check_max, clip) | ||
| 1201 | #define CONF_HANDLE_SSIZE_T(o, n, min, max) \ | ||
| 1202 | CONF_HANDLE_T_SIGNED(ssize_t, o, n, min, max, \ | ||
| 1203 | CONF_CHECK_MIN, CONF_CHECK_MAX, false) | ||
| 1204 | #define CONF_HANDLE_CHAR_P(o, n, d) \ | ||
| 1205 | if (CONF_MATCH(n)) { \ | ||
| 1206 | size_t cpylen = (vlen <= \ | ||
| 1207 | sizeof(o)-1) ? vlen : \ | ||
| 1208 | sizeof(o)-1; \ | ||
| 1209 | strncpy(o, v, cpylen); \ | ||
| 1210 | o[cpylen] = '\0'; \ | ||
| 1211 | CONF_CONTINUE; \ | ||
| 1212 | } | ||
| 1213 | |||
| 1214 | bool cur_opt_valid = true; | ||
| 1215 | |||
| 1216 | CONF_HANDLE_BOOL(opt_confirm_conf, "confirm_conf") | ||
| 1217 | if (initial_call) { | ||
| 1218 | continue; | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | CONF_HANDLE_BOOL(opt_abort, "abort") | ||
| 1222 | CONF_HANDLE_BOOL(opt_abort_conf, "abort_conf") | ||
| 1223 | CONF_HANDLE_BOOL(opt_trust_madvise, "trust_madvise") | ||
| 1224 | if (strncmp("metadata_thp", k, klen) == 0) { | ||
| 1225 | int m; | ||
| 1226 | bool match = false; | ||
| 1227 | for (m = 0; m < metadata_thp_mode_limit; m++) { | ||
| 1228 | if (strncmp(metadata_thp_mode_names[m], | ||
| 1229 | v, vlen) == 0) { | ||
| 1230 | opt_metadata_thp = m; | ||
| 1231 | match = true; | ||
| 1232 | break; | ||
| 1233 | } | ||
| 1234 | } | ||
| 1235 | if (!match) { | ||
| 1236 | CONF_ERROR("Invalid conf value", | ||
| 1237 | k, klen, v, vlen); | ||
| 1238 | } | ||
| 1239 | CONF_CONTINUE; | ||
| 1240 | } | ||
| 1241 | CONF_HANDLE_BOOL(opt_retain, "retain") | ||
| 1242 | if (strncmp("dss", k, klen) == 0) { | ||
| 1243 | int m; | ||
| 1244 | bool match = false; | ||
| 1245 | for (m = 0; m < dss_prec_limit; m++) { | ||
| 1246 | if (strncmp(dss_prec_names[m], v, vlen) | ||
| 1247 | == 0) { | ||
| 1248 | if (extent_dss_prec_set(m)) { | ||
| 1249 | CONF_ERROR( | ||
| 1250 | "Error setting dss", | ||
| 1251 | k, klen, v, vlen); | ||
| 1252 | } else { | ||
| 1253 | opt_dss = | ||
| 1254 | dss_prec_names[m]; | ||
| 1255 | match = true; | ||
| 1256 | break; | ||
| 1257 | } | ||
| 1258 | } | ||
| 1259 | } | ||
| 1260 | if (!match) { | ||
| 1261 | CONF_ERROR("Invalid conf value", | ||
| 1262 | k, klen, v, vlen); | ||
| 1263 | } | ||
| 1264 | CONF_CONTINUE; | ||
| 1265 | } | ||
| 1266 | if (CONF_MATCH("narenas")) { | ||
| 1267 | if (CONF_MATCH_VALUE("default")) { | ||
| 1268 | opt_narenas = 0; | ||
| 1269 | CONF_CONTINUE; | ||
| 1270 | } else { | ||
| 1271 | CONF_HANDLE_UNSIGNED(opt_narenas, | ||
| 1272 | "narenas", 1, UINT_MAX, | ||
| 1273 | CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, | ||
| 1274 | /* clip */ false) | ||
| 1275 | } | ||
| 1276 | } | ||
| 1277 | if (CONF_MATCH("narenas_ratio")) { | ||
| 1278 | char *end; | ||
| 1279 | bool err = fxp_parse(&opt_narenas_ratio, v, | ||
| 1280 | &end); | ||
| 1281 | if (err || (size_t)(end - v) != vlen) { | ||
| 1282 | CONF_ERROR("Invalid conf value", | ||
| 1283 | k, klen, v, vlen); | ||
| 1284 | } | ||
| 1285 | CONF_CONTINUE; | ||
| 1286 | } | ||
| 1287 | if (CONF_MATCH("bin_shards")) { | ||
| 1288 | const char *bin_shards_segment_cur = v; | ||
| 1289 | size_t vlen_left = vlen; | ||
| 1290 | do { | ||
| 1291 | size_t size_start; | ||
| 1292 | size_t size_end; | ||
| 1293 | size_t nshards; | ||
| 1294 | bool err = malloc_conf_multi_sizes_next( | ||
| 1295 | &bin_shards_segment_cur, &vlen_left, | ||
| 1296 | &size_start, &size_end, &nshards); | ||
| 1297 | if (err || bin_update_shard_size( | ||
| 1298 | bin_shard_sizes, size_start, | ||
| 1299 | size_end, nshards)) { | ||
| 1300 | CONF_ERROR( | ||
| 1301 | "Invalid settings for " | ||
| 1302 | "bin_shards", k, klen, v, | ||
| 1303 | vlen); | ||
| 1304 | break; | ||
| 1305 | } | ||
| 1306 | } while (vlen_left > 0); | ||
| 1307 | CONF_CONTINUE; | ||
| 1308 | } | ||
| 1309 | CONF_HANDLE_INT64_T(opt_mutex_max_spin, | ||
| 1310 | "mutex_max_spin", -1, INT64_MAX, CONF_CHECK_MIN, | ||
| 1311 | CONF_DONT_CHECK_MAX, false); | ||
| 1312 | CONF_HANDLE_SSIZE_T(opt_dirty_decay_ms, | ||
| 1313 | "dirty_decay_ms", -1, NSTIME_SEC_MAX * KQU(1000) < | ||
| 1314 | QU(SSIZE_MAX) ? NSTIME_SEC_MAX * KQU(1000) : | ||
| 1315 | SSIZE_MAX); | ||
| 1316 | CONF_HANDLE_SSIZE_T(opt_muzzy_decay_ms, | ||
| 1317 | "muzzy_decay_ms", -1, NSTIME_SEC_MAX * KQU(1000) < | ||
| 1318 | QU(SSIZE_MAX) ? NSTIME_SEC_MAX * KQU(1000) : | ||
| 1319 | SSIZE_MAX); | ||
| 1320 | CONF_HANDLE_BOOL(opt_stats_print, "stats_print") | ||
| 1321 | if (CONF_MATCH("stats_print_opts")) { | ||
| 1322 | init_opt_stats_opts(v, vlen, | ||
| 1323 | opt_stats_print_opts); | ||
| 1324 | CONF_CONTINUE; | ||
| 1325 | } | ||
| 1326 | CONF_HANDLE_INT64_T(opt_stats_interval, | ||
| 1327 | "stats_interval", -1, INT64_MAX, | ||
| 1328 | CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, false) | ||
| 1329 | if (CONF_MATCH("stats_interval_opts")) { | ||
| 1330 | init_opt_stats_opts(v, vlen, | ||
| 1331 | opt_stats_interval_opts); | ||
| 1332 | CONF_CONTINUE; | ||
| 1333 | } | ||
| 1334 | if (config_fill) { | ||
| 1335 | if (CONF_MATCH("junk")) { | ||
| 1336 | if (CONF_MATCH_VALUE("true")) { | ||
| 1337 | opt_junk = "true"; | ||
| 1338 | opt_junk_alloc = opt_junk_free = | ||
| 1339 | true; | ||
| 1340 | } else if (CONF_MATCH_VALUE("false")) { | ||
| 1341 | opt_junk = "false"; | ||
| 1342 | opt_junk_alloc = opt_junk_free = | ||
| 1343 | false; | ||
| 1344 | } else if (CONF_MATCH_VALUE("alloc")) { | ||
| 1345 | opt_junk = "alloc"; | ||
| 1346 | opt_junk_alloc = true; | ||
| 1347 | opt_junk_free = false; | ||
| 1348 | } else if (CONF_MATCH_VALUE("free")) { | ||
| 1349 | opt_junk = "free"; | ||
| 1350 | opt_junk_alloc = false; | ||
| 1351 | opt_junk_free = true; | ||
| 1352 | } else { | ||
| 1353 | CONF_ERROR( | ||
| 1354 | "Invalid conf value", | ||
| 1355 | k, klen, v, vlen); | ||
| 1356 | } | ||
| 1357 | CONF_CONTINUE; | ||
| 1358 | } | ||
| 1359 | CONF_HANDLE_BOOL(opt_zero, "zero") | ||
| 1360 | } | ||
| 1361 | if (config_utrace) { | ||
| 1362 | CONF_HANDLE_BOOL(opt_utrace, "utrace") | ||
| 1363 | } | ||
| 1364 | if (config_xmalloc) { | ||
| 1365 | CONF_HANDLE_BOOL(opt_xmalloc, "xmalloc") | ||
| 1366 | } | ||
| 1367 | if (config_enable_cxx) { | ||
| 1368 | CONF_HANDLE_BOOL( | ||
| 1369 | opt_experimental_infallible_new, | ||
| 1370 | "experimental_infallible_new") | ||
| 1371 | } | ||
| 1372 | |||
| 1373 | CONF_HANDLE_BOOL(opt_tcache, "tcache") | ||
| 1374 | CONF_HANDLE_SIZE_T(opt_tcache_max, "tcache_max", | ||
| 1375 | 0, TCACHE_MAXCLASS_LIMIT, CONF_DONT_CHECK_MIN, | ||
| 1376 | CONF_CHECK_MAX, /* clip */ true) | ||
| 1377 | if (CONF_MATCH("lg_tcache_max")) { | ||
| 1378 | size_t m; | ||
| 1379 | CONF_VALUE_READ(size_t, m) | ||
| 1380 | if (CONF_VALUE_READ_FAIL()) { | ||
| 1381 | CONF_ERROR("Invalid conf value", | ||
| 1382 | k, klen, v, vlen); | ||
| 1383 | } else { | ||
| 1384 | /* clip if necessary */ | ||
| 1385 | if (m > TCACHE_LG_MAXCLASS_LIMIT) { | ||
| 1386 | m = TCACHE_LG_MAXCLASS_LIMIT; | ||
| 1387 | } | ||
| 1388 | opt_tcache_max = (size_t)1 << m; | ||
| 1389 | } | ||
| 1390 | CONF_CONTINUE; | ||
| 1391 | } | ||
| 1392 | /* | ||
| 1393 | * Anyone trying to set a value outside -16 to 16 is | ||
| 1394 | * deeply confused. | ||
| 1395 | */ | ||
| 1396 | CONF_HANDLE_SSIZE_T(opt_lg_tcache_nslots_mul, | ||
| 1397 | "lg_tcache_nslots_mul", -16, 16) | ||
| 1398 | /* Ditto with values past 2048. */ | ||
| 1399 | CONF_HANDLE_UNSIGNED(opt_tcache_nslots_small_min, | ||
| 1400 | "tcache_nslots_small_min", 1, 2048, | ||
| 1401 | CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) | ||
| 1402 | CONF_HANDLE_UNSIGNED(opt_tcache_nslots_small_max, | ||
| 1403 | "tcache_nslots_small_max", 1, 2048, | ||
| 1404 | CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) | ||
| 1405 | CONF_HANDLE_UNSIGNED(opt_tcache_nslots_large, | ||
| 1406 | "tcache_nslots_large", 1, 2048, | ||
| 1407 | CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) | ||
| 1408 | CONF_HANDLE_SIZE_T(opt_tcache_gc_incr_bytes, | ||
| 1409 | "tcache_gc_incr_bytes", 1024, SIZE_T_MAX, | ||
| 1410 | CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, | ||
| 1411 | /* clip */ true) | ||
| 1412 | CONF_HANDLE_SIZE_T(opt_tcache_gc_delay_bytes, | ||
| 1413 | "tcache_gc_delay_bytes", 0, SIZE_T_MAX, | ||
| 1414 | CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, | ||
| 1415 | /* clip */ false) | ||
| 1416 | CONF_HANDLE_UNSIGNED(opt_lg_tcache_flush_small_div, | ||
| 1417 | "lg_tcache_flush_small_div", 1, 16, | ||
| 1418 | CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) | ||
| 1419 | CONF_HANDLE_UNSIGNED(opt_lg_tcache_flush_large_div, | ||
| 1420 | "lg_tcache_flush_large_div", 1, 16, | ||
| 1421 | CONF_CHECK_MIN, CONF_CHECK_MAX, /* clip */ true) | ||
| 1422 | |||
| 1423 | /* | ||
| 1424 | * The runtime option of oversize_threshold remains | ||
| 1425 | * undocumented. It may be tweaked in the next major | ||
| 1426 | * release (6.0). The default value 8M is rather | ||
| 1427 | * conservative / safe. Tuning it further down may | ||
| 1428 | * improve fragmentation a bit more, but may also cause | ||
| 1429 | * contention on the huge arena. | ||
| 1430 | */ | ||
| 1431 | CONF_HANDLE_SIZE_T(opt_oversize_threshold, | ||
| 1432 | "oversize_threshold", 0, SC_LARGE_MAXCLASS, | ||
| 1433 | CONF_DONT_CHECK_MIN, CONF_CHECK_MAX, false) | ||
| 1434 | CONF_HANDLE_SIZE_T(opt_lg_extent_max_active_fit, | ||
| 1435 | "lg_extent_max_active_fit", 0, | ||
| 1436 | (sizeof(size_t) << 3), CONF_DONT_CHECK_MIN, | ||
| 1437 | CONF_CHECK_MAX, false) | ||
| 1438 | |||
| 1439 | if (strncmp("percpu_arena", k, klen) == 0) { | ||
| 1440 | bool match = false; | ||
| 1441 | for (int m = percpu_arena_mode_names_base; m < | ||
| 1442 | percpu_arena_mode_names_limit; m++) { | ||
| 1443 | if (strncmp(percpu_arena_mode_names[m], | ||
| 1444 | v, vlen) == 0) { | ||
| 1445 | if (!have_percpu_arena) { | ||
| 1446 | CONF_ERROR( | ||
| 1447 | "No getcpu support", | ||
| 1448 | k, klen, v, vlen); | ||
| 1449 | } | ||
| 1450 | opt_percpu_arena = m; | ||
| 1451 | match = true; | ||
| 1452 | break; | ||
| 1453 | } | ||
| 1454 | } | ||
| 1455 | if (!match) { | ||
| 1456 | CONF_ERROR("Invalid conf value", | ||
| 1457 | k, klen, v, vlen); | ||
| 1458 | } | ||
| 1459 | CONF_CONTINUE; | ||
| 1460 | } | ||
| 1461 | CONF_HANDLE_BOOL(opt_background_thread, | ||
| 1462 | "background_thread"); | ||
| 1463 | CONF_HANDLE_SIZE_T(opt_max_background_threads, | ||
| 1464 | "max_background_threads", 1, | ||
| 1465 | opt_max_background_threads, | ||
| 1466 | CONF_CHECK_MIN, CONF_CHECK_MAX, | ||
| 1467 | true); | ||
| 1468 | CONF_HANDLE_BOOL(opt_hpa, "hpa") | ||
| 1469 | CONF_HANDLE_SIZE_T(opt_hpa_opts.slab_max_alloc, | ||
| 1470 | "hpa_slab_max_alloc", PAGE, HUGEPAGE, | ||
| 1471 | CONF_CHECK_MIN, CONF_CHECK_MAX, true); | ||
| 1472 | |||
| 1473 | /* | ||
| 1474 | * Accept either a ratio-based or an exact hugification | ||
| 1475 | * threshold. | ||
| 1476 | */ | ||
| 1477 | CONF_HANDLE_SIZE_T(opt_hpa_opts.hugification_threshold, | ||
| 1478 | "hpa_hugification_threshold", PAGE, HUGEPAGE, | ||
| 1479 | CONF_CHECK_MIN, CONF_CHECK_MAX, true); | ||
| 1480 | if (CONF_MATCH("hpa_hugification_threshold_ratio")) { | ||
| 1481 | fxp_t ratio; | ||
| 1482 | char *end; | ||
| 1483 | bool err = fxp_parse(&ratio, v, | ||
| 1484 | &end); | ||
| 1485 | if (err || (size_t)(end - v) != vlen | ||
| 1486 | || ratio > FXP_INIT_INT(1)) { | ||
| 1487 | CONF_ERROR("Invalid conf value", | ||
| 1488 | k, klen, v, vlen); | ||
| 1489 | } else { | ||
| 1490 | opt_hpa_opts.hugification_threshold = | ||
| 1491 | fxp_mul_frac(HUGEPAGE, ratio); | ||
| 1492 | } | ||
| 1493 | CONF_CONTINUE; | ||
| 1494 | } | ||
| 1495 | |||
| 1496 | CONF_HANDLE_UINT64_T( | ||
| 1497 | opt_hpa_opts.hugify_delay_ms, "hpa_hugify_delay_ms", | ||
| 1498 | 0, 0, CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, | ||
| 1499 | false); | ||
| 1500 | |||
| 1501 | CONF_HANDLE_UINT64_T( | ||
| 1502 | opt_hpa_opts.min_purge_interval_ms, | ||
| 1503 | "hpa_min_purge_interval_ms", 0, 0, | ||
| 1504 | CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false); | ||
| 1505 | |||
| 1506 | if (CONF_MATCH("hpa_dirty_mult")) { | ||
| 1507 | if (CONF_MATCH_VALUE("-1")) { | ||
| 1508 | opt_hpa_opts.dirty_mult = (fxp_t)-1; | ||
| 1509 | CONF_CONTINUE; | ||
| 1510 | } | ||
| 1511 | fxp_t ratio; | ||
| 1512 | char *end; | ||
| 1513 | bool err = fxp_parse(&ratio, v, | ||
| 1514 | &end); | ||
| 1515 | if (err || (size_t)(end - v) != vlen) { | ||
| 1516 | CONF_ERROR("Invalid conf value", | ||
| 1517 | k, klen, v, vlen); | ||
| 1518 | } else { | ||
| 1519 | opt_hpa_opts.dirty_mult = ratio; | ||
| 1520 | } | ||
| 1521 | CONF_CONTINUE; | ||
| 1522 | } | ||
| 1523 | |||
| 1524 | CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.nshards, | ||
| 1525 | "hpa_sec_nshards", 0, 0, CONF_CHECK_MIN, | ||
| 1526 | CONF_DONT_CHECK_MAX, true); | ||
| 1527 | CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.max_alloc, | ||
| 1528 | "hpa_sec_max_alloc", PAGE, 0, CONF_CHECK_MIN, | ||
| 1529 | CONF_DONT_CHECK_MAX, true); | ||
| 1530 | CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.max_bytes, | ||
| 1531 | "hpa_sec_max_bytes", PAGE, 0, CONF_CHECK_MIN, | ||
| 1532 | CONF_DONT_CHECK_MAX, true); | ||
| 1533 | CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.bytes_after_flush, | ||
| 1534 | "hpa_sec_bytes_after_flush", PAGE, 0, | ||
| 1535 | CONF_CHECK_MIN, CONF_DONT_CHECK_MAX, true); | ||
| 1536 | CONF_HANDLE_SIZE_T(opt_hpa_sec_opts.batch_fill_extra, | ||
| 1537 | "hpa_sec_batch_fill_extra", 0, HUGEPAGE_PAGES, | ||
| 1538 | CONF_CHECK_MIN, CONF_CHECK_MAX, true); | ||
| 1539 | |||
| 1540 | if (CONF_MATCH("slab_sizes")) { | ||
| 1541 | if (CONF_MATCH_VALUE("default")) { | ||
| 1542 | sc_data_init(sc_data); | ||
| 1543 | CONF_CONTINUE; | ||
| 1544 | } | ||
| 1545 | bool err; | ||
| 1546 | const char *slab_size_segment_cur = v; | ||
| 1547 | size_t vlen_left = vlen; | ||
| 1548 | do { | ||
| 1549 | size_t slab_start; | ||
| 1550 | size_t slab_end; | ||
| 1551 | size_t pgs; | ||
| 1552 | err = malloc_conf_multi_sizes_next( | ||
| 1553 | &slab_size_segment_cur, | ||
| 1554 | &vlen_left, &slab_start, &slab_end, | ||
| 1555 | &pgs); | ||
| 1556 | if (!err) { | ||
| 1557 | sc_data_update_slab_size( | ||
| 1558 | sc_data, slab_start, | ||
| 1559 | slab_end, (int)pgs); | ||
| 1560 | } else { | ||
| 1561 | CONF_ERROR("Invalid settings " | ||
| 1562 | "for slab_sizes", | ||
| 1563 | k, klen, v, vlen); | ||
| 1564 | } | ||
| 1565 | } while (!err && vlen_left > 0); | ||
| 1566 | CONF_CONTINUE; | ||
| 1567 | } | ||
| 1568 | if (config_prof) { | ||
| 1569 | CONF_HANDLE_BOOL(opt_prof, "prof") | ||
| 1570 | CONF_HANDLE_CHAR_P(opt_prof_prefix, | ||
| 1571 | "prof_prefix", "jeprof") | ||
| 1572 | CONF_HANDLE_BOOL(opt_prof_active, "prof_active") | ||
| 1573 | CONF_HANDLE_BOOL(opt_prof_thread_active_init, | ||
| 1574 | "prof_thread_active_init") | ||
| 1575 | CONF_HANDLE_SIZE_T(opt_lg_prof_sample, | ||
| 1576 | "lg_prof_sample", 0, (sizeof(uint64_t) << 3) | ||
| 1577 | - 1, CONF_DONT_CHECK_MIN, CONF_CHECK_MAX, | ||
| 1578 | true) | ||
| 1579 | CONF_HANDLE_BOOL(opt_prof_accum, "prof_accum") | ||
| 1580 | CONF_HANDLE_SSIZE_T(opt_lg_prof_interval, | ||
| 1581 | "lg_prof_interval", -1, | ||
| 1582 | (sizeof(uint64_t) << 3) - 1) | ||
| 1583 | CONF_HANDLE_BOOL(opt_prof_gdump, "prof_gdump") | ||
| 1584 | CONF_HANDLE_BOOL(opt_prof_final, "prof_final") | ||
| 1585 | CONF_HANDLE_BOOL(opt_prof_leak, "prof_leak") | ||
| 1586 | CONF_HANDLE_BOOL(opt_prof_leak_error, | ||
| 1587 | "prof_leak_error") | ||
| 1588 | CONF_HANDLE_BOOL(opt_prof_log, "prof_log") | ||
| 1589 | CONF_HANDLE_SSIZE_T(opt_prof_recent_alloc_max, | ||
| 1590 | "prof_recent_alloc_max", -1, SSIZE_MAX) | ||
| 1591 | CONF_HANDLE_BOOL(opt_prof_stats, "prof_stats") | ||
| 1592 | CONF_HANDLE_BOOL(opt_prof_sys_thread_name, | ||
| 1593 | "prof_sys_thread_name") | ||
| 1594 | if (CONF_MATCH("prof_time_resolution")) { | ||
| 1595 | if (CONF_MATCH_VALUE("default")) { | ||
| 1596 | opt_prof_time_res = | ||
| 1597 | prof_time_res_default; | ||
| 1598 | } else if (CONF_MATCH_VALUE("high")) { | ||
| 1599 | if (!config_high_res_timer) { | ||
| 1600 | CONF_ERROR( | ||
| 1601 | "No high resolution" | ||
| 1602 | " timer support", | ||
| 1603 | k, klen, v, vlen); | ||
| 1604 | } else { | ||
| 1605 | opt_prof_time_res = | ||
| 1606 | prof_time_res_high; | ||
| 1607 | } | ||
| 1608 | } else { | ||
| 1609 | CONF_ERROR("Invalid conf value", | ||
| 1610 | k, klen, v, vlen); | ||
| 1611 | } | ||
| 1612 | CONF_CONTINUE; | ||
| 1613 | } | ||
| 1614 | /* | ||
| 1615 | * Undocumented. When set to false, don't | ||
| 1616 | * correct for an unbiasing bug in jeprof | ||
| 1617 | * attribution. This can be handy if you want | ||
| 1618 | * to get consistent numbers from your binary | ||
| 1619 | * across different jemalloc versions, even if | ||
| 1620 | * those numbers are incorrect. The default is | ||
| 1621 | * true. | ||
| 1622 | */ | ||
| 1623 | CONF_HANDLE_BOOL(opt_prof_unbias, "prof_unbias") | ||
| 1624 | } | ||
| 1625 | if (config_log) { | ||
| 1626 | if (CONF_MATCH("log")) { | ||
| 1627 | size_t cpylen = ( | ||
| 1628 | vlen <= sizeof(log_var_names) ? | ||
| 1629 | vlen : sizeof(log_var_names) - 1); | ||
| 1630 | strncpy(log_var_names, v, cpylen); | ||
| 1631 | log_var_names[cpylen] = '\0'; | ||
| 1632 | CONF_CONTINUE; | ||
| 1633 | } | ||
| 1634 | } | ||
| 1635 | if (CONF_MATCH("thp")) { | ||
| 1636 | bool match = false; | ||
| 1637 | for (int m = 0; m < thp_mode_names_limit; m++) { | ||
| 1638 | if (strncmp(thp_mode_names[m],v, vlen) | ||
| 1639 | == 0) { | ||
| 1640 | if (!have_madvise_huge && !have_memcntl) { | ||
| 1641 | CONF_ERROR( | ||
| 1642 | "No THP support", | ||
| 1643 | k, klen, v, vlen); | ||
| 1644 | } | ||
| 1645 | opt_thp = m; | ||
| 1646 | match = true; | ||
| 1647 | break; | ||
| 1648 | } | ||
| 1649 | } | ||
| 1650 | if (!match) { | ||
| 1651 | CONF_ERROR("Invalid conf value", | ||
| 1652 | k, klen, v, vlen); | ||
| 1653 | } | ||
| 1654 | CONF_CONTINUE; | ||
| 1655 | } | ||
| 1656 | if (CONF_MATCH("zero_realloc")) { | ||
| 1657 | if (CONF_MATCH_VALUE("alloc")) { | ||
| 1658 | opt_zero_realloc_action | ||
| 1659 | = zero_realloc_action_alloc; | ||
| 1660 | } else if (CONF_MATCH_VALUE("free")) { | ||
| 1661 | opt_zero_realloc_action | ||
| 1662 | = zero_realloc_action_free; | ||
| 1663 | } else if (CONF_MATCH_VALUE("abort")) { | ||
| 1664 | opt_zero_realloc_action | ||
| 1665 | = zero_realloc_action_abort; | ||
| 1666 | } else { | ||
| 1667 | CONF_ERROR("Invalid conf value", | ||
| 1668 | k, klen, v, vlen); | ||
| 1669 | } | ||
| 1670 | CONF_CONTINUE; | ||
| 1671 | } | ||
| 1672 | if (config_uaf_detection && | ||
| 1673 | CONF_MATCH("lg_san_uaf_align")) { | ||
| 1674 | ssize_t a; | ||
| 1675 | CONF_VALUE_READ(ssize_t, a) | ||
| 1676 | if (CONF_VALUE_READ_FAIL() || a < -1) { | ||
| 1677 | CONF_ERROR("Invalid conf value", | ||
| 1678 | k, klen, v, vlen); | ||
| 1679 | } | ||
| 1680 | if (a == -1) { | ||
| 1681 | opt_lg_san_uaf_align = -1; | ||
| 1682 | CONF_CONTINUE; | ||
| 1683 | } | ||
| 1684 | |||
| 1685 | /* clip if necessary */ | ||
| 1686 | ssize_t max_allowed = (sizeof(size_t) << 3) - 1; | ||
| 1687 | ssize_t min_allowed = LG_PAGE; | ||
| 1688 | if (a > max_allowed) { | ||
| 1689 | a = max_allowed; | ||
| 1690 | } else if (a < min_allowed) { | ||
| 1691 | a = min_allowed; | ||
| 1692 | } | ||
| 1693 | |||
| 1694 | opt_lg_san_uaf_align = a; | ||
| 1695 | CONF_CONTINUE; | ||
| 1696 | } | ||
| 1697 | |||
| 1698 | CONF_HANDLE_SIZE_T(opt_san_guard_small, | ||
| 1699 | "san_guard_small", 0, SIZE_T_MAX, | ||
| 1700 | CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false) | ||
| 1701 | CONF_HANDLE_SIZE_T(opt_san_guard_large, | ||
| 1702 | "san_guard_large", 0, SIZE_T_MAX, | ||
| 1703 | CONF_DONT_CHECK_MIN, CONF_DONT_CHECK_MAX, false) | ||
| 1704 | |||
| 1705 | CONF_ERROR("Invalid conf pair", k, klen, v, vlen); | ||
| 1706 | #undef CONF_ERROR | ||
| 1707 | #undef CONF_CONTINUE | ||
| 1708 | #undef CONF_MATCH | ||
| 1709 | #undef CONF_MATCH_VALUE | ||
| 1710 | #undef CONF_HANDLE_BOOL | ||
| 1711 | #undef CONF_DONT_CHECK_MIN | ||
| 1712 | #undef CONF_CHECK_MIN | ||
| 1713 | #undef CONF_DONT_CHECK_MAX | ||
| 1714 | #undef CONF_CHECK_MAX | ||
| 1715 | #undef CONF_HANDLE_T | ||
| 1716 | #undef CONF_HANDLE_T_U | ||
| 1717 | #undef CONF_HANDLE_T_SIGNED | ||
| 1718 | #undef CONF_HANDLE_UNSIGNED | ||
| 1719 | #undef CONF_HANDLE_SIZE_T | ||
| 1720 | #undef CONF_HANDLE_SSIZE_T | ||
| 1721 | #undef CONF_HANDLE_CHAR_P | ||
| 1722 | /* Re-enable diagnostic "-Wtype-limits" */ | ||
| 1723 | JEMALLOC_DIAGNOSTIC_POP | ||
| 1724 | } | ||
| 1725 | if (opt_abort_conf && had_conf_error) { | ||
| 1726 | malloc_abort_invalid_conf(); | ||
| 1727 | } | ||
| 1728 | } | ||
| 1729 | atomic_store_b(&log_init_done, true, ATOMIC_RELEASE); | ||
| 1730 | } | ||
| 1731 | |||
| 1732 | static bool | ||
| 1733 | malloc_conf_init_check_deps(void) { | ||
| 1734 | if (opt_prof_leak_error && !opt_prof_final) { | ||
| 1735 | malloc_printf("<jemalloc>: prof_leak_error is set w/o " | ||
| 1736 | "prof_final.\n"); | ||
| 1737 | return true; | ||
| 1738 | } | ||
| 1739 | |||
| 1740 | return false; | ||
| 1741 | } | ||
| 1742 | |||
| 1743 | static void | ||
| 1744 | malloc_conf_init(sc_data_t *sc_data, unsigned bin_shard_sizes[SC_NBINS]) { | ||
| 1745 | const char *opts_cache[MALLOC_CONF_NSOURCES] = {NULL, NULL, NULL, NULL, | ||
| 1746 | NULL}; | ||
| 1747 | char buf[PATH_MAX + 1]; | ||
| 1748 | |||
| 1749 | /* The first call only set the confirm_conf option and opts_cache */ | ||
| 1750 | malloc_conf_init_helper(NULL, NULL, true, opts_cache, buf); | ||
| 1751 | malloc_conf_init_helper(sc_data, bin_shard_sizes, false, opts_cache, | ||
| 1752 | NULL); | ||
| 1753 | if (malloc_conf_init_check_deps()) { | ||
| 1754 | /* check_deps does warning msg only; abort below if needed. */ | ||
| 1755 | if (opt_abort_conf) { | ||
| 1756 | malloc_abort_invalid_conf(); | ||
| 1757 | } | ||
| 1758 | } | ||
| 1759 | } | ||
| 1760 | |||
| 1761 | #undef MALLOC_CONF_NSOURCES | ||
| 1762 | |||
| 1763 | static bool | ||
| 1764 | malloc_init_hard_needed(void) { | ||
| 1765 | if (malloc_initialized() || (IS_INITIALIZER && malloc_init_state == | ||
| 1766 | malloc_init_recursible)) { | ||
| 1767 | /* | ||
| 1768 | * Another thread initialized the allocator before this one | ||
| 1769 | * acquired init_lock, or this thread is the initializing | ||
| 1770 | * thread, and it is recursively allocating. | ||
| 1771 | */ | ||
| 1772 | return false; | ||
| 1773 | } | ||
| 1774 | #ifdef JEMALLOC_THREADED_INIT | ||
| 1775 | if (malloc_initializer != NO_INITIALIZER && !IS_INITIALIZER) { | ||
| 1776 | /* Busy-wait until the initializing thread completes. */ | ||
| 1777 | spin_t spinner = SPIN_INITIALIZER; | ||
| 1778 | do { | ||
| 1779 | malloc_mutex_unlock(TSDN_NULL, &init_lock); | ||
| 1780 | spin_adaptive(&spinner); | ||
| 1781 | malloc_mutex_lock(TSDN_NULL, &init_lock); | ||
| 1782 | } while (!malloc_initialized()); | ||
| 1783 | return false; | ||
| 1784 | } | ||
| 1785 | #endif | ||
| 1786 | return true; | ||
| 1787 | } | ||
| 1788 | |||
| 1789 | static bool | ||
| 1790 | malloc_init_hard_a0_locked() { | ||
| 1791 | malloc_initializer = INITIALIZER; | ||
| 1792 | |||
| 1793 | JEMALLOC_DIAGNOSTIC_PUSH | ||
| 1794 | JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS | ||
| 1795 | sc_data_t sc_data = {0}; | ||
| 1796 | JEMALLOC_DIAGNOSTIC_POP | ||
| 1797 | |||
| 1798 | /* | ||
| 1799 | * Ordering here is somewhat tricky; we need sc_boot() first, since that | ||
| 1800 | * determines what the size classes will be, and then | ||
| 1801 | * malloc_conf_init(), since any slab size tweaking will need to be done | ||
| 1802 | * before sz_boot and bin_info_boot, which assume that the values they | ||
| 1803 | * read out of sc_data_global are final. | ||
| 1804 | */ | ||
| 1805 | sc_boot(&sc_data); | ||
| 1806 | unsigned bin_shard_sizes[SC_NBINS]; | ||
| 1807 | bin_shard_sizes_boot(bin_shard_sizes); | ||
| 1808 | /* | ||
| 1809 | * prof_boot0 only initializes opt_prof_prefix. We need to do it before | ||
| 1810 | * we parse malloc_conf options, in case malloc_conf parsing overwrites | ||
| 1811 | * it. | ||
| 1812 | */ | ||
| 1813 | if (config_prof) { | ||
| 1814 | prof_boot0(); | ||
| 1815 | } | ||
| 1816 | malloc_conf_init(&sc_data, bin_shard_sizes); | ||
| 1817 | san_init(opt_lg_san_uaf_align); | ||
| 1818 | sz_boot(&sc_data, opt_cache_oblivious); | ||
| 1819 | bin_info_boot(&sc_data, bin_shard_sizes); | ||
| 1820 | |||
| 1821 | if (opt_stats_print) { | ||
| 1822 | /* Print statistics at exit. */ | ||
| 1823 | if (atexit(stats_print_atexit) != 0) { | ||
| 1824 | malloc_write("<jemalloc>: Error in atexit()\n"); | ||
| 1825 | if (opt_abort) { | ||
| 1826 | abort(); | ||
| 1827 | } | ||
| 1828 | } | ||
| 1829 | } | ||
| 1830 | |||
| 1831 | if (stats_boot()) { | ||
| 1832 | return true; | ||
| 1833 | } | ||
| 1834 | if (pages_boot()) { | ||
| 1835 | return true; | ||
| 1836 | } | ||
| 1837 | if (base_boot(TSDN_NULL)) { | ||
| 1838 | return true; | ||
| 1839 | } | ||
| 1840 | /* emap_global is static, hence zeroed. */ | ||
| 1841 | if (emap_init(&arena_emap_global, b0get(), /* zeroed */ true)) { | ||
| 1842 | return true; | ||
| 1843 | } | ||
| 1844 | if (extent_boot()) { | ||
| 1845 | return true; | ||
| 1846 | } | ||
| 1847 | if (ctl_boot()) { | ||
| 1848 | return true; | ||
| 1849 | } | ||
| 1850 | if (config_prof) { | ||
| 1851 | prof_boot1(); | ||
| 1852 | } | ||
| 1853 | if (opt_hpa && !hpa_supported()) { | ||
| 1854 | malloc_printf("<jemalloc>: HPA not supported in the current " | ||
| 1855 | "configuration; %s.", | ||
| 1856 | opt_abort_conf ? "aborting" : "disabling"); | ||
| 1857 | if (opt_abort_conf) { | ||
| 1858 | malloc_abort_invalid_conf(); | ||
| 1859 | } else { | ||
| 1860 | opt_hpa = false; | ||
| 1861 | } | ||
| 1862 | } | ||
| 1863 | if (arena_boot(&sc_data, b0get(), opt_hpa)) { | ||
| 1864 | return true; | ||
| 1865 | } | ||
| 1866 | if (tcache_boot(TSDN_NULL, b0get())) { | ||
| 1867 | return true; | ||
| 1868 | } | ||
| 1869 | if (malloc_mutex_init(&arenas_lock, "arenas", WITNESS_RANK_ARENAS, | ||
| 1870 | malloc_mutex_rank_exclusive)) { | ||
| 1871 | return true; | ||
| 1872 | } | ||
| 1873 | hook_boot(); | ||
| 1874 | /* | ||
| 1875 | * Create enough scaffolding to allow recursive allocation in | ||
| 1876 | * malloc_ncpus(). | ||
| 1877 | */ | ||
| 1878 | narenas_auto = 1; | ||
| 1879 | manual_arena_base = narenas_auto + 1; | ||
| 1880 | memset(arenas, 0, sizeof(arena_t *) * narenas_auto); | ||
| 1881 | /* | ||
| 1882 | * Initialize one arena here. The rest are lazily created in | ||
| 1883 | * arena_choose_hard(). | ||
| 1884 | */ | ||
| 1885 | if (arena_init(TSDN_NULL, 0, &arena_config_default) == NULL) { | ||
| 1886 | return true; | ||
| 1887 | } | ||
| 1888 | a0 = arena_get(TSDN_NULL, 0, false); | ||
| 1889 | |||
| 1890 | if (opt_hpa && !hpa_supported()) { | ||
| 1891 | malloc_printf("<jemalloc>: HPA not supported in the current " | ||
| 1892 | "configuration; %s.", | ||
| 1893 | opt_abort_conf ? "aborting" : "disabling"); | ||
| 1894 | if (opt_abort_conf) { | ||
| 1895 | malloc_abort_invalid_conf(); | ||
| 1896 | } else { | ||
| 1897 | opt_hpa = false; | ||
| 1898 | } | ||
| 1899 | } else if (opt_hpa) { | ||
| 1900 | hpa_shard_opts_t hpa_shard_opts = opt_hpa_opts; | ||
| 1901 | hpa_shard_opts.deferral_allowed = background_thread_enabled(); | ||
| 1902 | if (pa_shard_enable_hpa(TSDN_NULL, &a0->pa_shard, | ||
| 1903 | &hpa_shard_opts, &opt_hpa_sec_opts)) { | ||
| 1904 | return true; | ||
| 1905 | } | ||
| 1906 | } | ||
| 1907 | |||
| 1908 | malloc_init_state = malloc_init_a0_initialized; | ||
| 1909 | |||
| 1910 | return false; | ||
| 1911 | } | ||
| 1912 | |||
| 1913 | static bool | ||
| 1914 | malloc_init_hard_a0(void) { | ||
| 1915 | bool ret; | ||
| 1916 | |||
| 1917 | malloc_mutex_lock(TSDN_NULL, &init_lock); | ||
| 1918 | ret = malloc_init_hard_a0_locked(); | ||
| 1919 | malloc_mutex_unlock(TSDN_NULL, &init_lock); | ||
| 1920 | return ret; | ||
| 1921 | } | ||
| 1922 | |||
| 1923 | /* Initialize data structures which may trigger recursive allocation. */ | ||
| 1924 | static bool | ||
| 1925 | malloc_init_hard_recursible(void) { | ||
| 1926 | malloc_init_state = malloc_init_recursible; | ||
| 1927 | |||
| 1928 | ncpus = malloc_ncpus(); | ||
| 1929 | if (opt_percpu_arena != percpu_arena_disabled) { | ||
| 1930 | bool cpu_count_is_deterministic = | ||
| 1931 | malloc_cpu_count_is_deterministic(); | ||
| 1932 | if (!cpu_count_is_deterministic) { | ||
| 1933 | /* | ||
| 1934 | * If # of CPU is not deterministic, and narenas not | ||
| 1935 | * specified, disables per cpu arena since it may not | ||
| 1936 | * detect CPU IDs properly. | ||
| 1937 | */ | ||
| 1938 | if (opt_narenas == 0) { | ||
| 1939 | opt_percpu_arena = percpu_arena_disabled; | ||
| 1940 | malloc_write("<jemalloc>: Number of CPUs " | ||
| 1941 | "detected is not deterministic. Per-CPU " | ||
| 1942 | "arena disabled.\n"); | ||
| 1943 | if (opt_abort_conf) { | ||
| 1944 | malloc_abort_invalid_conf(); | ||
| 1945 | } | ||
| 1946 | if (opt_abort) { | ||
| 1947 | abort(); | ||
| 1948 | } | ||
| 1949 | } | ||
| 1950 | } | ||
| 1951 | } | ||
| 1952 | |||
| 1953 | #if (defined(JEMALLOC_HAVE_PTHREAD_ATFORK) && !defined(JEMALLOC_MUTEX_INIT_CB) \ | ||
| 1954 | && !defined(JEMALLOC_ZONE) && !defined(_WIN32) && \ | ||
| 1955 | !defined(__native_client__)) | ||
| 1956 | /* LinuxThreads' pthread_atfork() allocates. */ | ||
| 1957 | if (pthread_atfork(jemalloc_prefork, jemalloc_postfork_parent, | ||
| 1958 | jemalloc_postfork_child) != 0) { | ||
| 1959 | malloc_write("<jemalloc>: Error in pthread_atfork()\n"); | ||
| 1960 | if (opt_abort) { | ||
| 1961 | abort(); | ||
| 1962 | } | ||
| 1963 | return true; | ||
| 1964 | } | ||
| 1965 | #endif | ||
| 1966 | |||
| 1967 | if (background_thread_boot0()) { | ||
| 1968 | return true; | ||
| 1969 | } | ||
| 1970 | |||
| 1971 | return false; | ||
| 1972 | } | ||
| 1973 | |||
| 1974 | static unsigned | ||
| 1975 | malloc_narenas_default(void) { | ||
| 1976 | assert(ncpus > 0); | ||
| 1977 | /* | ||
| 1978 | * For SMP systems, create more than one arena per CPU by | ||
| 1979 | * default. | ||
| 1980 | */ | ||
| 1981 | if (ncpus > 1) { | ||
| 1982 | fxp_t fxp_ncpus = FXP_INIT_INT(ncpus); | ||
| 1983 | fxp_t goal = fxp_mul(fxp_ncpus, opt_narenas_ratio); | ||
| 1984 | uint32_t int_goal = fxp_round_nearest(goal); | ||
| 1985 | if (int_goal == 0) { | ||
| 1986 | return 1; | ||
| 1987 | } | ||
| 1988 | return int_goal; | ||
| 1989 | } else { | ||
| 1990 | return 1; | ||
| 1991 | } | ||
| 1992 | } | ||
| 1993 | |||
| 1994 | static percpu_arena_mode_t | ||
| 1995 | percpu_arena_as_initialized(percpu_arena_mode_t mode) { | ||
| 1996 | assert(!malloc_initialized()); | ||
| 1997 | assert(mode <= percpu_arena_disabled); | ||
| 1998 | |||
| 1999 | if (mode != percpu_arena_disabled) { | ||
| 2000 | mode += percpu_arena_mode_enabled_base; | ||
| 2001 | } | ||
| 2002 | |||
| 2003 | return mode; | ||
| 2004 | } | ||
| 2005 | |||
| 2006 | static bool | ||
| 2007 | malloc_init_narenas(void) { | ||
| 2008 | assert(ncpus > 0); | ||
| 2009 | |||
| 2010 | if (opt_percpu_arena != percpu_arena_disabled) { | ||
| 2011 | if (!have_percpu_arena || malloc_getcpu() < 0) { | ||
| 2012 | opt_percpu_arena = percpu_arena_disabled; | ||
| 2013 | malloc_printf("<jemalloc>: perCPU arena getcpu() not " | ||
| 2014 | "available. Setting narenas to %u.\n", opt_narenas ? | ||
| 2015 | opt_narenas : malloc_narenas_default()); | ||
| 2016 | if (opt_abort) { | ||
| 2017 | abort(); | ||
| 2018 | } | ||
| 2019 | } else { | ||
| 2020 | if (ncpus >= MALLOCX_ARENA_LIMIT) { | ||
| 2021 | malloc_printf("<jemalloc>: narenas w/ percpu" | ||
| 2022 | "arena beyond limit (%d)\n", ncpus); | ||
| 2023 | if (opt_abort) { | ||
| 2024 | abort(); | ||
| 2025 | } | ||
| 2026 | return true; | ||
| 2027 | } | ||
| 2028 | /* NB: opt_percpu_arena isn't fully initialized yet. */ | ||
| 2029 | if (percpu_arena_as_initialized(opt_percpu_arena) == | ||
| 2030 | per_phycpu_arena && ncpus % 2 != 0) { | ||
| 2031 | malloc_printf("<jemalloc>: invalid " | ||
| 2032 | "configuration -- per physical CPU arena " | ||
| 2033 | "with odd number (%u) of CPUs (no hyper " | ||
| 2034 | "threading?).\n", ncpus); | ||
| 2035 | if (opt_abort) | ||
| 2036 | abort(); | ||
| 2037 | } | ||
| 2038 | unsigned n = percpu_arena_ind_limit( | ||
| 2039 | percpu_arena_as_initialized(opt_percpu_arena)); | ||
| 2040 | if (opt_narenas < n) { | ||
| 2041 | /* | ||
| 2042 | * If narenas is specified with percpu_arena | ||
| 2043 | * enabled, actual narenas is set as the greater | ||
| 2044 | * of the two. percpu_arena_choose will be free | ||
| 2045 | * to use any of the arenas based on CPU | ||
| 2046 | * id. This is conservative (at a small cost) | ||
| 2047 | * but ensures correctness. | ||
| 2048 | * | ||
| 2049 | * If for some reason the ncpus determined at | ||
| 2050 | * boot is not the actual number (e.g. because | ||
| 2051 | * of affinity setting from numactl), reserving | ||
| 2052 | * narenas this way provides a workaround for | ||
| 2053 | * percpu_arena. | ||
| 2054 | */ | ||
| 2055 | opt_narenas = n; | ||
| 2056 | } | ||
| 2057 | } | ||
| 2058 | } | ||
| 2059 | if (opt_narenas == 0) { | ||
| 2060 | opt_narenas = malloc_narenas_default(); | ||
| 2061 | } | ||
| 2062 | assert(opt_narenas > 0); | ||
| 2063 | |||
| 2064 | narenas_auto = opt_narenas; | ||
| 2065 | /* | ||
| 2066 | * Limit the number of arenas to the indexing range of MALLOCX_ARENA(). | ||
| 2067 | */ | ||
| 2068 | if (narenas_auto >= MALLOCX_ARENA_LIMIT) { | ||
| 2069 | narenas_auto = MALLOCX_ARENA_LIMIT - 1; | ||
| 2070 | malloc_printf("<jemalloc>: Reducing narenas to limit (%d)\n", | ||
| 2071 | narenas_auto); | ||
| 2072 | } | ||
| 2073 | narenas_total_set(narenas_auto); | ||
| 2074 | if (arena_init_huge()) { | ||
| 2075 | narenas_total_inc(); | ||
| 2076 | } | ||
| 2077 | manual_arena_base = narenas_total_get(); | ||
| 2078 | |||
| 2079 | return false; | ||
| 2080 | } | ||
| 2081 | |||
| 2082 | static void | ||
| 2083 | malloc_init_percpu(void) { | ||
| 2084 | opt_percpu_arena = percpu_arena_as_initialized(opt_percpu_arena); | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | static bool | ||
| 2088 | malloc_init_hard_finish(void) { | ||
| 2089 | if (malloc_mutex_boot()) { | ||
| 2090 | return true; | ||
| 2091 | } | ||
| 2092 | |||
| 2093 | malloc_init_state = malloc_init_initialized; | ||
| 2094 | malloc_slow_flag_init(); | ||
| 2095 | |||
| 2096 | return false; | ||
| 2097 | } | ||
| 2098 | |||
| 2099 | static void | ||
| 2100 | malloc_init_hard_cleanup(tsdn_t *tsdn, bool reentrancy_set) { | ||
| 2101 | malloc_mutex_assert_owner(tsdn, &init_lock); | ||
| 2102 | malloc_mutex_unlock(tsdn, &init_lock); | ||
| 2103 | if (reentrancy_set) { | ||
| 2104 | assert(!tsdn_null(tsdn)); | ||
| 2105 | tsd_t *tsd = tsdn_tsd(tsdn); | ||
| 2106 | assert(tsd_reentrancy_level_get(tsd) > 0); | ||
| 2107 | post_reentrancy(tsd); | ||
| 2108 | } | ||
| 2109 | } | ||
| 2110 | |||
| 2111 | static bool | ||
| 2112 | malloc_init_hard(void) { | ||
| 2113 | tsd_t *tsd; | ||
| 2114 | |||
| 2115 | #if defined(_WIN32) && _WIN32_WINNT < 0x0600 | ||
| 2116 | _init_init_lock(); | ||
| 2117 | #endif | ||
| 2118 | malloc_mutex_lock(TSDN_NULL, &init_lock); | ||
| 2119 | |||
| 2120 | #define UNLOCK_RETURN(tsdn, ret, reentrancy) \ | ||
| 2121 | malloc_init_hard_cleanup(tsdn, reentrancy); \ | ||
| 2122 | return ret; | ||
| 2123 | |||
| 2124 | if (!malloc_init_hard_needed()) { | ||
| 2125 | UNLOCK_RETURN(TSDN_NULL, false, false) | ||
| 2126 | } | ||
| 2127 | |||
| 2128 | if (malloc_init_state != malloc_init_a0_initialized && | ||
| 2129 | malloc_init_hard_a0_locked()) { | ||
| 2130 | UNLOCK_RETURN(TSDN_NULL, true, false) | ||
| 2131 | } | ||
| 2132 | |||
| 2133 | malloc_mutex_unlock(TSDN_NULL, &init_lock); | ||
| 2134 | /* Recursive allocation relies on functional tsd. */ | ||
| 2135 | tsd = malloc_tsd_boot0(); | ||
| 2136 | if (tsd == NULL) { | ||
| 2137 | return true; | ||
| 2138 | } | ||
| 2139 | if (malloc_init_hard_recursible()) { | ||
| 2140 | return true; | ||
| 2141 | } | ||
| 2142 | |||
| 2143 | malloc_mutex_lock(tsd_tsdn(tsd), &init_lock); | ||
| 2144 | /* Set reentrancy level to 1 during init. */ | ||
| 2145 | pre_reentrancy(tsd, NULL); | ||
| 2146 | /* Initialize narenas before prof_boot2 (for allocation). */ | ||
| 2147 | if (malloc_init_narenas() | ||
| 2148 | || background_thread_boot1(tsd_tsdn(tsd), b0get())) { | ||
| 2149 | UNLOCK_RETURN(tsd_tsdn(tsd), true, true) | ||
| 2150 | } | ||
| 2151 | if (config_prof && prof_boot2(tsd, b0get())) { | ||
| 2152 | UNLOCK_RETURN(tsd_tsdn(tsd), true, true) | ||
| 2153 | } | ||
| 2154 | |||
| 2155 | malloc_init_percpu(); | ||
| 2156 | |||
| 2157 | if (malloc_init_hard_finish()) { | ||
| 2158 | UNLOCK_RETURN(tsd_tsdn(tsd), true, true) | ||
| 2159 | } | ||
| 2160 | post_reentrancy(tsd); | ||
| 2161 | malloc_mutex_unlock(tsd_tsdn(tsd), &init_lock); | ||
| 2162 | |||
| 2163 | witness_assert_lockless(witness_tsd_tsdn( | ||
| 2164 | tsd_witness_tsdp_get_unsafe(tsd))); | ||
| 2165 | malloc_tsd_boot1(); | ||
| 2166 | /* Update TSD after tsd_boot1. */ | ||
| 2167 | tsd = tsd_fetch(); | ||
| 2168 | if (opt_background_thread) { | ||
| 2169 | assert(have_background_thread); | ||
| 2170 | /* | ||
| 2171 | * Need to finish init & unlock first before creating background | ||
| 2172 | * threads (pthread_create depends on malloc). ctl_init (which | ||
| 2173 | * sets isthreaded) needs to be called without holding any lock. | ||
| 2174 | */ | ||
| 2175 | background_thread_ctl_init(tsd_tsdn(tsd)); | ||
| 2176 | if (background_thread_create(tsd, 0)) { | ||
| 2177 | return true; | ||
| 2178 | } | ||
| 2179 | } | ||
| 2180 | #undef UNLOCK_RETURN | ||
| 2181 | return false; | ||
| 2182 | } | ||
| 2183 | |||
| 2184 | /* | ||
| 2185 | * End initialization functions. | ||
| 2186 | */ | ||
| 2187 | /******************************************************************************/ | ||
| 2188 | /* | ||
| 2189 | * Begin allocation-path internal functions and data structures. | ||
| 2190 | */ | ||
| 2191 | |||
| 2192 | /* | ||
| 2193 | * Settings determined by the documented behavior of the allocation functions. | ||
| 2194 | */ | ||
| 2195 | typedef struct static_opts_s static_opts_t; | ||
| 2196 | struct static_opts_s { | ||
| 2197 | /* Whether or not allocation size may overflow. */ | ||
| 2198 | bool may_overflow; | ||
| 2199 | |||
| 2200 | /* | ||
| 2201 | * Whether or not allocations (with alignment) of size 0 should be | ||
| 2202 | * treated as size 1. | ||
| 2203 | */ | ||
| 2204 | bool bump_empty_aligned_alloc; | ||
| 2205 | /* | ||
| 2206 | * Whether to assert that allocations are not of size 0 (after any | ||
| 2207 | * bumping). | ||
| 2208 | */ | ||
| 2209 | bool assert_nonempty_alloc; | ||
| 2210 | |||
| 2211 | /* | ||
| 2212 | * Whether or not to modify the 'result' argument to malloc in case of | ||
| 2213 | * error. | ||
| 2214 | */ | ||
| 2215 | bool null_out_result_on_error; | ||
| 2216 | /* Whether to set errno when we encounter an error condition. */ | ||
| 2217 | bool set_errno_on_error; | ||
| 2218 | |||
| 2219 | /* | ||
| 2220 | * The minimum valid alignment for functions requesting aligned storage. | ||
| 2221 | */ | ||
| 2222 | size_t min_alignment; | ||
| 2223 | |||
| 2224 | /* The error string to use if we oom. */ | ||
| 2225 | const char *oom_string; | ||
| 2226 | /* The error string to use if the passed-in alignment is invalid. */ | ||
| 2227 | const char *invalid_alignment_string; | ||
| 2228 | |||
| 2229 | /* | ||
| 2230 | * False if we're configured to skip some time-consuming operations. | ||
| 2231 | * | ||
| 2232 | * This isn't really a malloc "behavior", but it acts as a useful | ||
| 2233 | * summary of several other static (or at least, static after program | ||
| 2234 | * initialization) options. | ||
| 2235 | */ | ||
| 2236 | bool slow; | ||
| 2237 | /* | ||
| 2238 | * Return size. | ||
| 2239 | */ | ||
| 2240 | bool usize; | ||
| 2241 | }; | ||
| 2242 | |||
| 2243 | JEMALLOC_ALWAYS_INLINE void | ||
| 2244 | static_opts_init(static_opts_t *static_opts) { | ||
| 2245 | static_opts->may_overflow = false; | ||
| 2246 | static_opts->bump_empty_aligned_alloc = false; | ||
| 2247 | static_opts->assert_nonempty_alloc = false; | ||
| 2248 | static_opts->null_out_result_on_error = false; | ||
| 2249 | static_opts->set_errno_on_error = false; | ||
| 2250 | static_opts->min_alignment = 0; | ||
| 2251 | static_opts->oom_string = ""; | ||
| 2252 | static_opts->invalid_alignment_string = ""; | ||
| 2253 | static_opts->slow = false; | ||
| 2254 | static_opts->usize = false; | ||
| 2255 | } | ||
| 2256 | |||
| 2257 | /* | ||
| 2258 | * These correspond to the macros in jemalloc/jemalloc_macros.h. Broadly, we | ||
| 2259 | * should have one constant here per magic value there. Note however that the | ||
| 2260 | * representations need not be related. | ||
| 2261 | */ | ||
| 2262 | #define TCACHE_IND_NONE ((unsigned)-1) | ||
| 2263 | #define TCACHE_IND_AUTOMATIC ((unsigned)-2) | ||
| 2264 | #define ARENA_IND_AUTOMATIC ((unsigned)-1) | ||
| 2265 | |||
| 2266 | typedef struct dynamic_opts_s dynamic_opts_t; | ||
| 2267 | struct dynamic_opts_s { | ||
| 2268 | void **result; | ||
| 2269 | size_t usize; | ||
| 2270 | size_t num_items; | ||
| 2271 | size_t item_size; | ||
| 2272 | size_t alignment; | ||
| 2273 | bool zero; | ||
| 2274 | unsigned tcache_ind; | ||
| 2275 | unsigned arena_ind; | ||
| 2276 | }; | ||
| 2277 | |||
| 2278 | JEMALLOC_ALWAYS_INLINE void | ||
| 2279 | dynamic_opts_init(dynamic_opts_t *dynamic_opts) { | ||
| 2280 | dynamic_opts->result = NULL; | ||
| 2281 | dynamic_opts->usize = 0; | ||
| 2282 | dynamic_opts->num_items = 0; | ||
| 2283 | dynamic_opts->item_size = 0; | ||
| 2284 | dynamic_opts->alignment = 0; | ||
| 2285 | dynamic_opts->zero = false; | ||
| 2286 | dynamic_opts->tcache_ind = TCACHE_IND_AUTOMATIC; | ||
| 2287 | dynamic_opts->arena_ind = ARENA_IND_AUTOMATIC; | ||
| 2288 | } | ||
| 2289 | |||
| 2290 | /* | ||
| 2291 | * ind parameter is optional and is only checked and filled if alignment == 0; | ||
| 2292 | * return true if result is out of range. | ||
| 2293 | */ | ||
| 2294 | JEMALLOC_ALWAYS_INLINE bool | ||
| 2295 | aligned_usize_get(size_t size, size_t alignment, size_t *usize, szind_t *ind, | ||
| 2296 | bool bump_empty_aligned_alloc) { | ||
| 2297 | assert(usize != NULL); | ||
| 2298 | if (alignment == 0) { | ||
| 2299 | if (ind != NULL) { | ||
| 2300 | *ind = sz_size2index(size); | ||
| 2301 | if (unlikely(*ind >= SC_NSIZES)) { | ||
| 2302 | return true; | ||
| 2303 | } | ||
| 2304 | *usize = sz_index2size(*ind); | ||
| 2305 | assert(*usize > 0 && *usize <= SC_LARGE_MAXCLASS); | ||
| 2306 | return false; | ||
| 2307 | } | ||
| 2308 | *usize = sz_s2u(size); | ||
| 2309 | } else { | ||
| 2310 | if (bump_empty_aligned_alloc && unlikely(size == 0)) { | ||
| 2311 | size = 1; | ||
| 2312 | } | ||
| 2313 | *usize = sz_sa2u(size, alignment); | ||
| 2314 | } | ||
| 2315 | if (unlikely(*usize == 0 || *usize > SC_LARGE_MAXCLASS)) { | ||
| 2316 | return true; | ||
| 2317 | } | ||
| 2318 | return false; | ||
| 2319 | } | ||
| 2320 | |||
| 2321 | JEMALLOC_ALWAYS_INLINE bool | ||
| 2322 | zero_get(bool guarantee, bool slow) { | ||
| 2323 | if (config_fill && slow && unlikely(opt_zero)) { | ||
| 2324 | return true; | ||
| 2325 | } else { | ||
| 2326 | return guarantee; | ||
| 2327 | } | ||
| 2328 | } | ||
| 2329 | |||
| 2330 | JEMALLOC_ALWAYS_INLINE tcache_t * | ||
| 2331 | tcache_get_from_ind(tsd_t *tsd, unsigned tcache_ind, bool slow, bool is_alloc) { | ||
| 2332 | tcache_t *tcache; | ||
| 2333 | if (tcache_ind == TCACHE_IND_AUTOMATIC) { | ||
| 2334 | if (likely(!slow)) { | ||
| 2335 | /* Getting tcache ptr unconditionally. */ | ||
| 2336 | tcache = tsd_tcachep_get(tsd); | ||
| 2337 | assert(tcache == tcache_get(tsd)); | ||
| 2338 | } else if (is_alloc || | ||
| 2339 | likely(tsd_reentrancy_level_get(tsd) == 0)) { | ||
| 2340 | tcache = tcache_get(tsd); | ||
| 2341 | } else { | ||
| 2342 | tcache = NULL; | ||
| 2343 | } | ||
| 2344 | } else { | ||
| 2345 | /* | ||
| 2346 | * Should not specify tcache on deallocation path when being | ||
| 2347 | * reentrant. | ||
| 2348 | */ | ||
| 2349 | assert(is_alloc || tsd_reentrancy_level_get(tsd) == 0 || | ||
| 2350 | tsd_state_nocleanup(tsd)); | ||
| 2351 | if (tcache_ind == TCACHE_IND_NONE) { | ||
| 2352 | tcache = NULL; | ||
| 2353 | } else { | ||
| 2354 | tcache = tcaches_get(tsd, tcache_ind); | ||
| 2355 | } | ||
| 2356 | } | ||
| 2357 | return tcache; | ||
| 2358 | } | ||
| 2359 | |||
| 2360 | /* Return true if a manual arena is specified and arena_get() OOMs. */ | ||
| 2361 | JEMALLOC_ALWAYS_INLINE bool | ||
| 2362 | arena_get_from_ind(tsd_t *tsd, unsigned arena_ind, arena_t **arena_p) { | ||
| 2363 | if (arena_ind == ARENA_IND_AUTOMATIC) { | ||
| 2364 | /* | ||
| 2365 | * In case of automatic arena management, we defer arena | ||
| 2366 | * computation until as late as we can, hoping to fill the | ||
| 2367 | * allocation out of the tcache. | ||
| 2368 | */ | ||
| 2369 | *arena_p = NULL; | ||
| 2370 | } else { | ||
| 2371 | *arena_p = arena_get(tsd_tsdn(tsd), arena_ind, true); | ||
| 2372 | if (unlikely(*arena_p == NULL) && arena_ind >= narenas_auto) { | ||
| 2373 | return true; | ||
| 2374 | } | ||
| 2375 | } | ||
| 2376 | return false; | ||
| 2377 | } | ||
| 2378 | |||
| 2379 | /* ind is ignored if dopts->alignment > 0. */ | ||
| 2380 | JEMALLOC_ALWAYS_INLINE void * | ||
| 2381 | imalloc_no_sample(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd, | ||
| 2382 | size_t size, size_t usize, szind_t ind) { | ||
| 2383 | /* Fill in the tcache. */ | ||
| 2384 | tcache_t *tcache = tcache_get_from_ind(tsd, dopts->tcache_ind, | ||
| 2385 | sopts->slow, /* is_alloc */ true); | ||
| 2386 | |||
| 2387 | /* Fill in the arena. */ | ||
| 2388 | arena_t *arena; | ||
| 2389 | if (arena_get_from_ind(tsd, dopts->arena_ind, &arena)) { | ||
| 2390 | return NULL; | ||
| 2391 | } | ||
| 2392 | |||
| 2393 | if (unlikely(dopts->alignment != 0)) { | ||
| 2394 | return ipalloct(tsd_tsdn(tsd), usize, dopts->alignment, | ||
| 2395 | dopts->zero, tcache, arena); | ||
| 2396 | } | ||
| 2397 | |||
| 2398 | return iallocztm(tsd_tsdn(tsd), size, ind, dopts->zero, tcache, false, | ||
| 2399 | arena, sopts->slow); | ||
| 2400 | } | ||
| 2401 | |||
| 2402 | JEMALLOC_ALWAYS_INLINE void * | ||
| 2403 | imalloc_sample(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd, | ||
| 2404 | size_t usize, szind_t ind) { | ||
| 2405 | void *ret; | ||
| 2406 | |||
| 2407 | /* | ||
| 2408 | * For small allocations, sampling bumps the usize. If so, we allocate | ||
| 2409 | * from the ind_large bucket. | ||
| 2410 | */ | ||
| 2411 | szind_t ind_large; | ||
| 2412 | size_t bumped_usize = usize; | ||
| 2413 | |||
| 2414 | dopts->alignment = prof_sample_align(dopts->alignment); | ||
| 2415 | if (usize <= SC_SMALL_MAXCLASS) { | ||
| 2416 | assert(((dopts->alignment == 0) ? | ||
| 2417 | sz_s2u(SC_LARGE_MINCLASS) : | ||
| 2418 | sz_sa2u(SC_LARGE_MINCLASS, dopts->alignment)) | ||
| 2419 | == SC_LARGE_MINCLASS); | ||
| 2420 | ind_large = sz_size2index(SC_LARGE_MINCLASS); | ||
| 2421 | bumped_usize = sz_s2u(SC_LARGE_MINCLASS); | ||
| 2422 | ret = imalloc_no_sample(sopts, dopts, tsd, bumped_usize, | ||
| 2423 | bumped_usize, ind_large); | ||
| 2424 | if (unlikely(ret == NULL)) { | ||
| 2425 | return NULL; | ||
| 2426 | } | ||
| 2427 | arena_prof_promote(tsd_tsdn(tsd), ret, usize); | ||
| 2428 | } else { | ||
| 2429 | ret = imalloc_no_sample(sopts, dopts, tsd, usize, usize, ind); | ||
| 2430 | } | ||
| 2431 | assert(prof_sample_aligned(ret)); | ||
| 2432 | |||
| 2433 | return ret; | ||
| 2434 | } | ||
| 2435 | |||
| 2436 | /* | ||
| 2437 | * Returns true if the allocation will overflow, and false otherwise. Sets | ||
| 2438 | * *size to the product either way. | ||
| 2439 | */ | ||
| 2440 | JEMALLOC_ALWAYS_INLINE bool | ||
| 2441 | compute_size_with_overflow(bool may_overflow, dynamic_opts_t *dopts, | ||
| 2442 | size_t *size) { | ||
| 2443 | /* | ||
| 2444 | * This function is just num_items * item_size, except that we may have | ||
| 2445 | * to check for overflow. | ||
| 2446 | */ | ||
| 2447 | |||
| 2448 | if (!may_overflow) { | ||
| 2449 | assert(dopts->num_items == 1); | ||
| 2450 | *size = dopts->item_size; | ||
| 2451 | return false; | ||
| 2452 | } | ||
| 2453 | |||
| 2454 | /* A size_t with its high-half bits all set to 1. */ | ||
| 2455 | static const size_t high_bits = SIZE_T_MAX << (sizeof(size_t) * 8 / 2); | ||
| 2456 | |||
| 2457 | *size = dopts->item_size * dopts->num_items; | ||
| 2458 | |||
| 2459 | if (unlikely(*size == 0)) { | ||
| 2460 | return (dopts->num_items != 0 && dopts->item_size != 0); | ||
| 2461 | } | ||
| 2462 | |||
| 2463 | /* | ||
| 2464 | * We got a non-zero size, but we don't know if we overflowed to get | ||
| 2465 | * there. To avoid having to do a divide, we'll be clever and note that | ||
| 2466 | * if both A and B can be represented in N/2 bits, then their product | ||
| 2467 | * can be represented in N bits (without the possibility of overflow). | ||
| 2468 | */ | ||
| 2469 | if (likely((high_bits & (dopts->num_items | dopts->item_size)) == 0)) { | ||
| 2470 | return false; | ||
| 2471 | } | ||
| 2472 | if (likely(*size / dopts->item_size == dopts->num_items)) { | ||
| 2473 | return false; | ||
| 2474 | } | ||
| 2475 | return true; | ||
| 2476 | } | ||
| 2477 | |||
| 2478 | JEMALLOC_ALWAYS_INLINE int | ||
| 2479 | imalloc_body(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd) { | ||
| 2480 | /* Where the actual allocated memory will live. */ | ||
| 2481 | void *allocation = NULL; | ||
| 2482 | /* Filled in by compute_size_with_overflow below. */ | ||
| 2483 | size_t size = 0; | ||
| 2484 | /* | ||
| 2485 | * The zero initialization for ind is actually dead store, in that its | ||
| 2486 | * value is reset before any branch on its value is taken. Sometimes | ||
| 2487 | * though, it's convenient to pass it as arguments before this point. | ||
| 2488 | * To avoid undefined behavior then, we initialize it with dummy stores. | ||
| 2489 | */ | ||
| 2490 | szind_t ind = 0; | ||
| 2491 | /* usize will always be properly initialized. */ | ||
| 2492 | size_t usize; | ||
| 2493 | |||
| 2494 | /* Reentrancy is only checked on slow path. */ | ||
| 2495 | int8_t reentrancy_level; | ||
| 2496 | |||
| 2497 | /* Compute the amount of memory the user wants. */ | ||
| 2498 | if (unlikely(compute_size_with_overflow(sopts->may_overflow, dopts, | ||
| 2499 | &size))) { | ||
| 2500 | goto label_oom; | ||
| 2501 | } | ||
| 2502 | |||
| 2503 | if (unlikely(dopts->alignment < sopts->min_alignment | ||
| 2504 | || (dopts->alignment & (dopts->alignment - 1)) != 0)) { | ||
| 2505 | goto label_invalid_alignment; | ||
| 2506 | } | ||
| 2507 | |||
| 2508 | /* This is the beginning of the "core" algorithm. */ | ||
| 2509 | dopts->zero = zero_get(dopts->zero, sopts->slow); | ||
| 2510 | if (aligned_usize_get(size, dopts->alignment, &usize, &ind, | ||
| 2511 | sopts->bump_empty_aligned_alloc)) { | ||
| 2512 | goto label_oom; | ||
| 2513 | } | ||
| 2514 | dopts->usize = usize; | ||
| 2515 | /* Validate the user input. */ | ||
| 2516 | if (sopts->assert_nonempty_alloc) { | ||
| 2517 | assert (size != 0); | ||
| 2518 | } | ||
| 2519 | |||
| 2520 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 2521 | |||
| 2522 | /* | ||
| 2523 | * If we need to handle reentrancy, we can do it out of a | ||
| 2524 | * known-initialized arena (i.e. arena 0). | ||
| 2525 | */ | ||
| 2526 | reentrancy_level = tsd_reentrancy_level_get(tsd); | ||
| 2527 | if (sopts->slow && unlikely(reentrancy_level > 0)) { | ||
| 2528 | /* | ||
| 2529 | * We should never specify particular arenas or tcaches from | ||
| 2530 | * within our internal allocations. | ||
| 2531 | */ | ||
| 2532 | assert(dopts->tcache_ind == TCACHE_IND_AUTOMATIC || | ||
| 2533 | dopts->tcache_ind == TCACHE_IND_NONE); | ||
| 2534 | assert(dopts->arena_ind == ARENA_IND_AUTOMATIC); | ||
| 2535 | dopts->tcache_ind = TCACHE_IND_NONE; | ||
| 2536 | /* We know that arena 0 has already been initialized. */ | ||
| 2537 | dopts->arena_ind = 0; | ||
| 2538 | } | ||
| 2539 | |||
| 2540 | /* | ||
| 2541 | * If dopts->alignment > 0, then ind is still 0, but usize was computed | ||
| 2542 | * in the previous if statement. Down the positive alignment path, | ||
| 2543 | * imalloc_no_sample and imalloc_sample will ignore ind. | ||
| 2544 | */ | ||
| 2545 | |||
| 2546 | /* If profiling is on, get our profiling context. */ | ||
| 2547 | if (config_prof && opt_prof) { | ||
| 2548 | bool prof_active = prof_active_get_unlocked(); | ||
| 2549 | bool sample_event = te_prof_sample_event_lookahead(tsd, usize); | ||
| 2550 | prof_tctx_t *tctx = prof_alloc_prep(tsd, prof_active, | ||
| 2551 | sample_event); | ||
| 2552 | |||
| 2553 | emap_alloc_ctx_t alloc_ctx; | ||
| 2554 | if (likely((uintptr_t)tctx == (uintptr_t)1U)) { | ||
| 2555 | alloc_ctx.slab = (usize <= SC_SMALL_MAXCLASS); | ||
| 2556 | allocation = imalloc_no_sample( | ||
| 2557 | sopts, dopts, tsd, usize, usize, ind); | ||
| 2558 | } else if ((uintptr_t)tctx > (uintptr_t)1U) { | ||
| 2559 | allocation = imalloc_sample( | ||
| 2560 | sopts, dopts, tsd, usize, ind); | ||
| 2561 | alloc_ctx.slab = false; | ||
| 2562 | } else { | ||
| 2563 | allocation = NULL; | ||
| 2564 | } | ||
| 2565 | |||
| 2566 | if (unlikely(allocation == NULL)) { | ||
| 2567 | prof_alloc_rollback(tsd, tctx); | ||
| 2568 | goto label_oom; | ||
| 2569 | } | ||
| 2570 | prof_malloc(tsd, allocation, size, usize, &alloc_ctx, tctx); | ||
| 2571 | } else { | ||
| 2572 | assert(!opt_prof); | ||
| 2573 | allocation = imalloc_no_sample(sopts, dopts, tsd, size, usize, | ||
| 2574 | ind); | ||
| 2575 | if (unlikely(allocation == NULL)) { | ||
| 2576 | goto label_oom; | ||
| 2577 | } | ||
| 2578 | } | ||
| 2579 | |||
| 2580 | /* | ||
| 2581 | * Allocation has been done at this point. We still have some | ||
| 2582 | * post-allocation work to do though. | ||
| 2583 | */ | ||
| 2584 | |||
| 2585 | thread_alloc_event(tsd, usize); | ||
| 2586 | |||
| 2587 | assert(dopts->alignment == 0 | ||
| 2588 | || ((uintptr_t)allocation & (dopts->alignment - 1)) == ZU(0)); | ||
| 2589 | |||
| 2590 | assert(usize == isalloc(tsd_tsdn(tsd), allocation)); | ||
| 2591 | |||
| 2592 | if (config_fill && sopts->slow && !dopts->zero | ||
| 2593 | && unlikely(opt_junk_alloc)) { | ||
| 2594 | junk_alloc_callback(allocation, usize); | ||
| 2595 | } | ||
| 2596 | |||
| 2597 | if (sopts->slow) { | ||
| 2598 | UTRACE(0, size, allocation); | ||
| 2599 | } | ||
| 2600 | |||
| 2601 | /* Success! */ | ||
| 2602 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 2603 | *dopts->result = allocation; | ||
| 2604 | return 0; | ||
| 2605 | |||
| 2606 | label_oom: | ||
| 2607 | if (unlikely(sopts->slow) && config_xmalloc && unlikely(opt_xmalloc)) { | ||
| 2608 | malloc_write(sopts->oom_string); | ||
| 2609 | abort(); | ||
| 2610 | } | ||
| 2611 | |||
| 2612 | if (sopts->slow) { | ||
| 2613 | UTRACE(NULL, size, NULL); | ||
| 2614 | } | ||
| 2615 | |||
| 2616 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 2617 | |||
| 2618 | if (sopts->set_errno_on_error) { | ||
| 2619 | set_errno(ENOMEM); | ||
| 2620 | } | ||
| 2621 | |||
| 2622 | if (sopts->null_out_result_on_error) { | ||
| 2623 | *dopts->result = NULL; | ||
| 2624 | } | ||
| 2625 | |||
| 2626 | return ENOMEM; | ||
| 2627 | |||
| 2628 | /* | ||
| 2629 | * This label is only jumped to by one goto; we move it out of line | ||
| 2630 | * anyways to avoid obscuring the non-error paths, and for symmetry with | ||
| 2631 | * the oom case. | ||
| 2632 | */ | ||
| 2633 | label_invalid_alignment: | ||
| 2634 | if (config_xmalloc && unlikely(opt_xmalloc)) { | ||
| 2635 | malloc_write(sopts->invalid_alignment_string); | ||
| 2636 | abort(); | ||
| 2637 | } | ||
| 2638 | |||
| 2639 | if (sopts->set_errno_on_error) { | ||
| 2640 | set_errno(EINVAL); | ||
| 2641 | } | ||
| 2642 | |||
| 2643 | if (sopts->slow) { | ||
| 2644 | UTRACE(NULL, size, NULL); | ||
| 2645 | } | ||
| 2646 | |||
| 2647 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 2648 | |||
| 2649 | if (sopts->null_out_result_on_error) { | ||
| 2650 | *dopts->result = NULL; | ||
| 2651 | } | ||
| 2652 | |||
| 2653 | return EINVAL; | ||
| 2654 | } | ||
| 2655 | |||
| 2656 | JEMALLOC_ALWAYS_INLINE bool | ||
| 2657 | imalloc_init_check(static_opts_t *sopts, dynamic_opts_t *dopts) { | ||
| 2658 | if (unlikely(!malloc_initialized()) && unlikely(malloc_init())) { | ||
| 2659 | if (config_xmalloc && unlikely(opt_xmalloc)) { | ||
| 2660 | malloc_write(sopts->oom_string); | ||
| 2661 | abort(); | ||
| 2662 | } | ||
| 2663 | UTRACE(NULL, dopts->num_items * dopts->item_size, NULL); | ||
| 2664 | set_errno(ENOMEM); | ||
| 2665 | *dopts->result = NULL; | ||
| 2666 | |||
| 2667 | return false; | ||
| 2668 | } | ||
| 2669 | |||
| 2670 | return true; | ||
| 2671 | } | ||
| 2672 | |||
| 2673 | /* Returns the errno-style error code of the allocation. */ | ||
| 2674 | JEMALLOC_ALWAYS_INLINE int | ||
| 2675 | imalloc(static_opts_t *sopts, dynamic_opts_t *dopts) { | ||
| 2676 | if (tsd_get_allocates() && !imalloc_init_check(sopts, dopts)) { | ||
| 2677 | return ENOMEM; | ||
| 2678 | } | ||
| 2679 | |||
| 2680 | /* We always need the tsd. Let's grab it right away. */ | ||
| 2681 | tsd_t *tsd = tsd_fetch(); | ||
| 2682 | assert(tsd); | ||
| 2683 | if (likely(tsd_fast(tsd))) { | ||
| 2684 | /* Fast and common path. */ | ||
| 2685 | tsd_assert_fast(tsd); | ||
| 2686 | sopts->slow = false; | ||
| 2687 | return imalloc_body(sopts, dopts, tsd); | ||
| 2688 | } else { | ||
| 2689 | if (!tsd_get_allocates() && !imalloc_init_check(sopts, dopts)) { | ||
| 2690 | return ENOMEM; | ||
| 2691 | } | ||
| 2692 | |||
| 2693 | sopts->slow = true; | ||
| 2694 | return imalloc_body(sopts, dopts, tsd); | ||
| 2695 | } | ||
| 2696 | } | ||
| 2697 | |||
| 2698 | JEMALLOC_NOINLINE | ||
| 2699 | void * | ||
| 2700 | malloc_default(size_t size, size_t *usize) { | ||
| 2701 | void *ret; | ||
| 2702 | static_opts_t sopts; | ||
| 2703 | dynamic_opts_t dopts; | ||
| 2704 | |||
| 2705 | /* | ||
| 2706 | * This variant has logging hook on exit but not on entry. It's callled | ||
| 2707 | * only by je_malloc, below, which emits the entry one for us (and, if | ||
| 2708 | * it calls us, does so only via tail call). | ||
| 2709 | */ | ||
| 2710 | |||
| 2711 | static_opts_init(&sopts); | ||
| 2712 | dynamic_opts_init(&dopts); | ||
| 2713 | |||
| 2714 | sopts.null_out_result_on_error = true; | ||
| 2715 | sopts.set_errno_on_error = true; | ||
| 2716 | sopts.oom_string = "<jemalloc>: Error in malloc(): out of memory\n"; | ||
| 2717 | |||
| 2718 | dopts.result = &ret; | ||
| 2719 | dopts.num_items = 1; | ||
| 2720 | dopts.item_size = size; | ||
| 2721 | |||
| 2722 | imalloc(&sopts, &dopts); | ||
| 2723 | /* | ||
| 2724 | * Note that this branch gets optimized away -- it immediately follows | ||
| 2725 | * the check on tsd_fast that sets sopts.slow. | ||
| 2726 | */ | ||
| 2727 | if (sopts.slow) { | ||
| 2728 | uintptr_t args[3] = {size}; | ||
| 2729 | hook_invoke_alloc(hook_alloc_malloc, ret, (uintptr_t)ret, args); | ||
| 2730 | } | ||
| 2731 | |||
| 2732 | LOG("core.malloc.exit", "result: %p", ret); | ||
| 2733 | |||
| 2734 | if (usize) *usize = dopts.usize; | ||
| 2735 | return ret; | ||
| 2736 | } | ||
| 2737 | |||
| 2738 | /******************************************************************************/ | ||
| 2739 | /* | ||
| 2740 | * Begin malloc(3)-compatible functions. | ||
| 2741 | */ | ||
| 2742 | |||
| 2743 | static inline void *je_malloc_internal(size_t size, size_t *usize) { | ||
| 2744 | return imalloc_fastpath(size, &malloc_default, usize); | ||
| 2745 | } | ||
| 2746 | |||
| 2747 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 2748 | void JEMALLOC_NOTHROW * | ||
| 2749 | JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1) | ||
| 2750 | je_malloc(size_t size) { | ||
| 2751 | return je_malloc_internal(size, NULL); | ||
| 2752 | } | ||
| 2753 | |||
| 2754 | JEMALLOC_EXPORT int JEMALLOC_NOTHROW | ||
| 2755 | JEMALLOC_ATTR(nonnull(1)) | ||
| 2756 | je_posix_memalign(void **memptr, size_t alignment, size_t size) { | ||
| 2757 | int ret; | ||
| 2758 | static_opts_t sopts; | ||
| 2759 | dynamic_opts_t dopts; | ||
| 2760 | |||
| 2761 | LOG("core.posix_memalign.entry", "mem ptr: %p, alignment: %zu, " | ||
| 2762 | "size: %zu", memptr, alignment, size); | ||
| 2763 | |||
| 2764 | static_opts_init(&sopts); | ||
| 2765 | dynamic_opts_init(&dopts); | ||
| 2766 | |||
| 2767 | sopts.bump_empty_aligned_alloc = true; | ||
| 2768 | sopts.min_alignment = sizeof(void *); | ||
| 2769 | sopts.oom_string = | ||
| 2770 | "<jemalloc>: Error allocating aligned memory: out of memory\n"; | ||
| 2771 | sopts.invalid_alignment_string = | ||
| 2772 | "<jemalloc>: Error allocating aligned memory: invalid alignment\n"; | ||
| 2773 | |||
| 2774 | dopts.result = memptr; | ||
| 2775 | dopts.num_items = 1; | ||
| 2776 | dopts.item_size = size; | ||
| 2777 | dopts.alignment = alignment; | ||
| 2778 | |||
| 2779 | ret = imalloc(&sopts, &dopts); | ||
| 2780 | if (sopts.slow) { | ||
| 2781 | uintptr_t args[3] = {(uintptr_t)memptr, (uintptr_t)alignment, | ||
| 2782 | (uintptr_t)size}; | ||
| 2783 | hook_invoke_alloc(hook_alloc_posix_memalign, *memptr, | ||
| 2784 | (uintptr_t)ret, args); | ||
| 2785 | } | ||
| 2786 | |||
| 2787 | LOG("core.posix_memalign.exit", "result: %d, alloc ptr: %p", ret, | ||
| 2788 | *memptr); | ||
| 2789 | |||
| 2790 | return ret; | ||
| 2791 | } | ||
| 2792 | |||
| 2793 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 2794 | void JEMALLOC_NOTHROW * | ||
| 2795 | JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(2) | ||
| 2796 | je_aligned_alloc(size_t alignment, size_t size) { | ||
| 2797 | void *ret; | ||
| 2798 | |||
| 2799 | static_opts_t sopts; | ||
| 2800 | dynamic_opts_t dopts; | ||
| 2801 | |||
| 2802 | LOG("core.aligned_alloc.entry", "alignment: %zu, size: %zu\n", | ||
| 2803 | alignment, size); | ||
| 2804 | |||
| 2805 | static_opts_init(&sopts); | ||
| 2806 | dynamic_opts_init(&dopts); | ||
| 2807 | |||
| 2808 | sopts.bump_empty_aligned_alloc = true; | ||
| 2809 | sopts.null_out_result_on_error = true; | ||
| 2810 | sopts.set_errno_on_error = true; | ||
| 2811 | sopts.min_alignment = 1; | ||
| 2812 | sopts.oom_string = | ||
| 2813 | "<jemalloc>: Error allocating aligned memory: out of memory\n"; | ||
| 2814 | sopts.invalid_alignment_string = | ||
| 2815 | "<jemalloc>: Error allocating aligned memory: invalid alignment\n"; | ||
| 2816 | |||
| 2817 | dopts.result = &ret; | ||
| 2818 | dopts.num_items = 1; | ||
| 2819 | dopts.item_size = size; | ||
| 2820 | dopts.alignment = alignment; | ||
| 2821 | |||
| 2822 | imalloc(&sopts, &dopts); | ||
| 2823 | if (sopts.slow) { | ||
| 2824 | uintptr_t args[3] = {(uintptr_t)alignment, (uintptr_t)size}; | ||
| 2825 | hook_invoke_alloc(hook_alloc_aligned_alloc, ret, | ||
| 2826 | (uintptr_t)ret, args); | ||
| 2827 | } | ||
| 2828 | |||
| 2829 | LOG("core.aligned_alloc.exit", "result: %p", ret); | ||
| 2830 | |||
| 2831 | return ret; | ||
| 2832 | } | ||
| 2833 | |||
| 2834 | static void *je_calloc_internal(size_t num, size_t size, size_t *usize) { | ||
| 2835 | void *ret; | ||
| 2836 | static_opts_t sopts; | ||
| 2837 | dynamic_opts_t dopts; | ||
| 2838 | |||
| 2839 | LOG("core.calloc.entry", "num: %zu, size: %zu\n", num, size); | ||
| 2840 | |||
| 2841 | static_opts_init(&sopts); | ||
| 2842 | dynamic_opts_init(&dopts); | ||
| 2843 | |||
| 2844 | sopts.may_overflow = true; | ||
| 2845 | sopts.null_out_result_on_error = true; | ||
| 2846 | sopts.set_errno_on_error = true; | ||
| 2847 | sopts.oom_string = "<jemalloc>: Error in calloc(): out of memory\n"; | ||
| 2848 | |||
| 2849 | dopts.result = &ret; | ||
| 2850 | dopts.num_items = num; | ||
| 2851 | dopts.item_size = size; | ||
| 2852 | dopts.zero = true; | ||
| 2853 | |||
| 2854 | imalloc(&sopts, &dopts); | ||
| 2855 | if (sopts.slow) { | ||
| 2856 | uintptr_t args[3] = {(uintptr_t)num, (uintptr_t)size}; | ||
| 2857 | hook_invoke_alloc(hook_alloc_calloc, ret, (uintptr_t)ret, args); | ||
| 2858 | } | ||
| 2859 | |||
| 2860 | LOG("core.calloc.exit", "result: %p", ret); | ||
| 2861 | |||
| 2862 | if (usize) *usize = dopts.usize; | ||
| 2863 | return ret; | ||
| 2864 | } | ||
| 2865 | |||
| 2866 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 2867 | void JEMALLOC_NOTHROW * | ||
| 2868 | JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE2(1, 2) | ||
| 2869 | je_calloc(size_t num, size_t size) { | ||
| 2870 | return je_calloc_internal(num, size, NULL); | ||
| 2871 | } | ||
| 2872 | |||
| 2873 | JEMALLOC_ALWAYS_INLINE void | ||
| 2874 | ifree(tsd_t *tsd, void *ptr, tcache_t *tcache, bool slow_path, size_t *usable) { | ||
| 2875 | if (!slow_path) { | ||
| 2876 | tsd_assert_fast(tsd); | ||
| 2877 | } | ||
| 2878 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 2879 | if (tsd_reentrancy_level_get(tsd) != 0) { | ||
| 2880 | assert(slow_path); | ||
| 2881 | } | ||
| 2882 | |||
| 2883 | assert(ptr != NULL); | ||
| 2884 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 2885 | |||
| 2886 | emap_alloc_ctx_t alloc_ctx; | ||
| 2887 | emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, | ||
| 2888 | &alloc_ctx); | ||
| 2889 | assert(alloc_ctx.szind != SC_NSIZES); | ||
| 2890 | |||
| 2891 | size_t usize = sz_index2size(alloc_ctx.szind); | ||
| 2892 | if (config_prof && opt_prof) { | ||
| 2893 | prof_free(tsd, ptr, usize, &alloc_ctx); | ||
| 2894 | } | ||
| 2895 | |||
| 2896 | if (likely(!slow_path)) { | ||
| 2897 | idalloctm(tsd_tsdn(tsd), ptr, tcache, &alloc_ctx, false, | ||
| 2898 | false); | ||
| 2899 | } else { | ||
| 2900 | if (config_fill && slow_path && opt_junk_free) { | ||
| 2901 | junk_free_callback(ptr, usize); | ||
| 2902 | } | ||
| 2903 | idalloctm(tsd_tsdn(tsd), ptr, tcache, &alloc_ctx, false, | ||
| 2904 | true); | ||
| 2905 | } | ||
| 2906 | thread_dalloc_event(tsd, usize); | ||
| 2907 | if (usable) *usable = usize; | ||
| 2908 | } | ||
| 2909 | |||
| 2910 | JEMALLOC_ALWAYS_INLINE bool | ||
| 2911 | maybe_check_alloc_ctx(tsd_t *tsd, void *ptr, emap_alloc_ctx_t *alloc_ctx) { | ||
| 2912 | if (config_opt_size_checks) { | ||
| 2913 | emap_alloc_ctx_t dbg_ctx; | ||
| 2914 | emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, | ||
| 2915 | &dbg_ctx); | ||
| 2916 | if (alloc_ctx->szind != dbg_ctx.szind) { | ||
| 2917 | safety_check_fail_sized_dealloc( | ||
| 2918 | /* current_dealloc */ true, ptr, | ||
| 2919 | /* true_size */ sz_size2index(dbg_ctx.szind), | ||
| 2920 | /* input_size */ sz_size2index(alloc_ctx->szind)); | ||
| 2921 | return true; | ||
| 2922 | } | ||
| 2923 | if (alloc_ctx->slab != dbg_ctx.slab) { | ||
| 2924 | safety_check_fail( | ||
| 2925 | "Internal heap corruption detected: " | ||
| 2926 | "mismatch in slab bit"); | ||
| 2927 | return true; | ||
| 2928 | } | ||
| 2929 | } | ||
| 2930 | return false; | ||
| 2931 | } | ||
| 2932 | |||
| 2933 | JEMALLOC_ALWAYS_INLINE void | ||
| 2934 | isfree(tsd_t *tsd, void *ptr, size_t usize, tcache_t *tcache, bool slow_path) { | ||
| 2935 | if (!slow_path) { | ||
| 2936 | tsd_assert_fast(tsd); | ||
| 2937 | } | ||
| 2938 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 2939 | if (tsd_reentrancy_level_get(tsd) != 0) { | ||
| 2940 | assert(slow_path); | ||
| 2941 | } | ||
| 2942 | |||
| 2943 | assert(ptr != NULL); | ||
| 2944 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 2945 | |||
| 2946 | emap_alloc_ctx_t alloc_ctx; | ||
| 2947 | if (!config_prof) { | ||
| 2948 | alloc_ctx.szind = sz_size2index(usize); | ||
| 2949 | alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); | ||
| 2950 | } else { | ||
| 2951 | if (likely(!prof_sample_aligned(ptr))) { | ||
| 2952 | /* | ||
| 2953 | * When the ptr is not page aligned, it was not sampled. | ||
| 2954 | * usize can be trusted to determine szind and slab. | ||
| 2955 | */ | ||
| 2956 | alloc_ctx.szind = sz_size2index(usize); | ||
| 2957 | alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); | ||
| 2958 | } else if (opt_prof) { | ||
| 2959 | emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, | ||
| 2960 | ptr, &alloc_ctx); | ||
| 2961 | |||
| 2962 | if (config_opt_safety_checks) { | ||
| 2963 | /* Small alloc may have !slab (sampled). */ | ||
| 2964 | if (unlikely(alloc_ctx.szind != | ||
| 2965 | sz_size2index(usize))) { | ||
| 2966 | safety_check_fail_sized_dealloc( | ||
| 2967 | /* current_dealloc */ true, ptr, | ||
| 2968 | /* true_size */ sz_index2size( | ||
| 2969 | alloc_ctx.szind), | ||
| 2970 | /* input_size */ usize); | ||
| 2971 | } | ||
| 2972 | } | ||
| 2973 | } else { | ||
| 2974 | alloc_ctx.szind = sz_size2index(usize); | ||
| 2975 | alloc_ctx.slab = (alloc_ctx.szind < SC_NBINS); | ||
| 2976 | } | ||
| 2977 | } | ||
| 2978 | bool fail = maybe_check_alloc_ctx(tsd, ptr, &alloc_ctx); | ||
| 2979 | if (fail) { | ||
| 2980 | /* | ||
| 2981 | * This is a heap corruption bug. In real life we'll crash; for | ||
| 2982 | * the unit test we just want to avoid breaking anything too | ||
| 2983 | * badly to get a test result out. Let's leak instead of trying | ||
| 2984 | * to free. | ||
| 2985 | */ | ||
| 2986 | return; | ||
| 2987 | } | ||
| 2988 | |||
| 2989 | if (config_prof && opt_prof) { | ||
| 2990 | prof_free(tsd, ptr, usize, &alloc_ctx); | ||
| 2991 | } | ||
| 2992 | if (likely(!slow_path)) { | ||
| 2993 | isdalloct(tsd_tsdn(tsd), ptr, usize, tcache, &alloc_ctx, | ||
| 2994 | false); | ||
| 2995 | } else { | ||
| 2996 | if (config_fill && slow_path && opt_junk_free) { | ||
| 2997 | junk_free_callback(ptr, usize); | ||
| 2998 | } | ||
| 2999 | isdalloct(tsd_tsdn(tsd), ptr, usize, tcache, &alloc_ctx, | ||
| 3000 | true); | ||
| 3001 | } | ||
| 3002 | thread_dalloc_event(tsd, usize); | ||
| 3003 | } | ||
| 3004 | |||
| 3005 | JEMALLOC_NOINLINE | ||
| 3006 | void | ||
| 3007 | free_default(void *ptr, size_t *usize) { | ||
| 3008 | UTRACE(ptr, 0, 0); | ||
| 3009 | if (likely(ptr != NULL)) { | ||
| 3010 | /* | ||
| 3011 | * We avoid setting up tsd fully (e.g. tcache, arena binding) | ||
| 3012 | * based on only free() calls -- other activities trigger the | ||
| 3013 | * minimal to full transition. This is because free() may | ||
| 3014 | * happen during thread shutdown after tls deallocation: if a | ||
| 3015 | * thread never had any malloc activities until then, a | ||
| 3016 | * fully-setup tsd won't be destructed properly. | ||
| 3017 | */ | ||
| 3018 | tsd_t *tsd = tsd_fetch_min(); | ||
| 3019 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3020 | |||
| 3021 | if (likely(tsd_fast(tsd))) { | ||
| 3022 | tcache_t *tcache = tcache_get_from_ind(tsd, | ||
| 3023 | TCACHE_IND_AUTOMATIC, /* slow */ false, | ||
| 3024 | /* is_alloc */ false); | ||
| 3025 | ifree(tsd, ptr, tcache, /* slow */ false, usize); | ||
| 3026 | } else { | ||
| 3027 | tcache_t *tcache = tcache_get_from_ind(tsd, | ||
| 3028 | TCACHE_IND_AUTOMATIC, /* slow */ true, | ||
| 3029 | /* is_alloc */ false); | ||
| 3030 | uintptr_t args_raw[3] = {(uintptr_t)ptr}; | ||
| 3031 | hook_invoke_dalloc(hook_dalloc_free, ptr, args_raw); | ||
| 3032 | ifree(tsd, ptr, tcache, /* slow */ true, usize); | ||
| 3033 | } | ||
| 3034 | |||
| 3035 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3036 | } | ||
| 3037 | } | ||
| 3038 | |||
| 3039 | JEMALLOC_ALWAYS_INLINE bool | ||
| 3040 | free_fastpath_nonfast_aligned(void *ptr, bool check_prof) { | ||
| 3041 | /* | ||
| 3042 | * free_fastpath do not handle two uncommon cases: 1) sampled profiled | ||
| 3043 | * objects and 2) sampled junk & stash for use-after-free detection. | ||
| 3044 | * Both have special alignments which are used to escape the fastpath. | ||
| 3045 | * | ||
| 3046 | * prof_sample is page-aligned, which covers the UAF check when both | ||
| 3047 | * are enabled (the assertion below). Avoiding redundant checks since | ||
| 3048 | * this is on the fastpath -- at most one runtime branch from this. | ||
| 3049 | */ | ||
| 3050 | if (config_debug && cache_bin_nonfast_aligned(ptr)) { | ||
| 3051 | assert(prof_sample_aligned(ptr)); | ||
| 3052 | } | ||
| 3053 | |||
| 3054 | if (config_prof && check_prof) { | ||
| 3055 | /* When prof is enabled, the prof_sample alignment is enough. */ | ||
| 3056 | if (prof_sample_aligned(ptr)) { | ||
| 3057 | return true; | ||
| 3058 | } else { | ||
| 3059 | return false; | ||
| 3060 | } | ||
| 3061 | } | ||
| 3062 | |||
| 3063 | if (config_uaf_detection) { | ||
| 3064 | if (cache_bin_nonfast_aligned(ptr)) { | ||
| 3065 | return true; | ||
| 3066 | } else { | ||
| 3067 | return false; | ||
| 3068 | } | ||
| 3069 | } | ||
| 3070 | |||
| 3071 | return false; | ||
| 3072 | } | ||
| 3073 | |||
| 3074 | /* Returns whether or not the free attempt was successful. */ | ||
| 3075 | JEMALLOC_ALWAYS_INLINE | ||
| 3076 | bool free_fastpath(void *ptr, size_t size, bool size_hint, size_t *usable_size) { | ||
| 3077 | tsd_t *tsd = tsd_get(false); | ||
| 3078 | /* The branch gets optimized away unless tsd_get_allocates(). */ | ||
| 3079 | if (unlikely(tsd == NULL)) { | ||
| 3080 | return false; | ||
| 3081 | } | ||
| 3082 | /* | ||
| 3083 | * The tsd_fast() / initialized checks are folded into the branch | ||
| 3084 | * testing (deallocated_after >= threshold) later in this function. | ||
| 3085 | * The threshold will be set to 0 when !tsd_fast. | ||
| 3086 | */ | ||
| 3087 | assert(tsd_fast(tsd) || | ||
| 3088 | *tsd_thread_deallocated_next_event_fastp_get_unsafe(tsd) == 0); | ||
| 3089 | |||
| 3090 | emap_alloc_ctx_t alloc_ctx; | ||
| 3091 | if (!size_hint) { | ||
| 3092 | bool err = emap_alloc_ctx_try_lookup_fast(tsd, | ||
| 3093 | &arena_emap_global, ptr, &alloc_ctx); | ||
| 3094 | |||
| 3095 | /* Note: profiled objects will have alloc_ctx.slab set */ | ||
| 3096 | if (unlikely(err || !alloc_ctx.slab || | ||
| 3097 | free_fastpath_nonfast_aligned(ptr, | ||
| 3098 | /* check_prof */ false))) { | ||
| 3099 | return false; | ||
| 3100 | } | ||
| 3101 | assert(alloc_ctx.szind != SC_NSIZES); | ||
| 3102 | } else { | ||
| 3103 | /* | ||
| 3104 | * Check for both sizes that are too large, and for sampled / | ||
| 3105 | * special aligned objects. The alignment check will also check | ||
| 3106 | * for null ptr. | ||
| 3107 | */ | ||
| 3108 | if (unlikely(size > SC_LOOKUP_MAXCLASS || | ||
| 3109 | free_fastpath_nonfast_aligned(ptr, | ||
| 3110 | /* check_prof */ true))) { | ||
| 3111 | return false; | ||
| 3112 | } | ||
| 3113 | alloc_ctx.szind = sz_size2index_lookup(size); | ||
| 3114 | /* Max lookup class must be small. */ | ||
| 3115 | assert(alloc_ctx.szind < SC_NBINS); | ||
| 3116 | /* This is a dead store, except when opt size checking is on. */ | ||
| 3117 | alloc_ctx.slab = true; | ||
| 3118 | } | ||
| 3119 | /* | ||
| 3120 | * Currently the fastpath only handles small sizes. The branch on | ||
| 3121 | * SC_LOOKUP_MAXCLASS makes sure of it. This lets us avoid checking | ||
| 3122 | * tcache szind upper limit (i.e. tcache_maxclass) as well. | ||
| 3123 | */ | ||
| 3124 | assert(alloc_ctx.slab); | ||
| 3125 | |||
| 3126 | uint64_t deallocated, threshold; | ||
| 3127 | te_free_fastpath_ctx(tsd, &deallocated, &threshold); | ||
| 3128 | |||
| 3129 | size_t usize = sz_index2size(alloc_ctx.szind); | ||
| 3130 | uint64_t deallocated_after = deallocated + usize; | ||
| 3131 | /* | ||
| 3132 | * Check for events and tsd non-nominal (fast_threshold will be set to | ||
| 3133 | * 0) in a single branch. Note that this handles the uninitialized case | ||
| 3134 | * as well (TSD init will be triggered on the non-fastpath). Therefore | ||
| 3135 | * anything depends on a functional TSD (e.g. the alloc_ctx sanity check | ||
| 3136 | * below) needs to be after this branch. | ||
| 3137 | */ | ||
| 3138 | if (unlikely(deallocated_after >= threshold)) { | ||
| 3139 | return false; | ||
| 3140 | } | ||
| 3141 | assert(tsd_fast(tsd)); | ||
| 3142 | bool fail = maybe_check_alloc_ctx(tsd, ptr, &alloc_ctx); | ||
| 3143 | if (fail) { | ||
| 3144 | /* See the comment in isfree. */ | ||
| 3145 | if (usable_size) *usable_size = usize; | ||
| 3146 | return true; | ||
| 3147 | } | ||
| 3148 | |||
| 3149 | tcache_t *tcache = tcache_get_from_ind(tsd, TCACHE_IND_AUTOMATIC, | ||
| 3150 | /* slow */ false, /* is_alloc */ false); | ||
| 3151 | cache_bin_t *bin = &tcache->bins[alloc_ctx.szind]; | ||
| 3152 | |||
| 3153 | /* | ||
| 3154 | * If junking were enabled, this is where we would do it. It's not | ||
| 3155 | * though, since we ensured above that we're on the fast path. Assert | ||
| 3156 | * that to double-check. | ||
| 3157 | */ | ||
| 3158 | assert(!opt_junk_free); | ||
| 3159 | |||
| 3160 | if (!cache_bin_dalloc_easy(bin, ptr)) { | ||
| 3161 | return false; | ||
| 3162 | } | ||
| 3163 | |||
| 3164 | *tsd_thread_deallocatedp_get(tsd) = deallocated_after; | ||
| 3165 | |||
| 3166 | if (usable_size) *usable_size = usize; | ||
| 3167 | return true; | ||
| 3168 | } | ||
| 3169 | |||
| 3170 | static inline void je_free_internal(void *ptr, size_t *usize) { | ||
| 3171 | LOG("core.free.entry", "ptr: %p", ptr); | ||
| 3172 | |||
| 3173 | if (!free_fastpath(ptr, 0, false, usize)) { | ||
| 3174 | free_default(ptr, usize); | ||
| 3175 | } | ||
| 3176 | |||
| 3177 | LOG("core.free.exit", ""); | ||
| 3178 | } | ||
| 3179 | |||
| 3180 | JEMALLOC_EXPORT void JEMALLOC_NOTHROW | ||
| 3181 | je_free(void *ptr) { | ||
| 3182 | je_free_internal(ptr, NULL); | ||
| 3183 | } | ||
| 3184 | |||
| 3185 | /* | ||
| 3186 | * End malloc(3)-compatible functions. | ||
| 3187 | */ | ||
| 3188 | /******************************************************************************/ | ||
| 3189 | /* | ||
| 3190 | * Begin non-standard override functions. | ||
| 3191 | */ | ||
| 3192 | |||
| 3193 | #ifdef JEMALLOC_OVERRIDE_MEMALIGN | ||
| 3194 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 3195 | void JEMALLOC_NOTHROW * | ||
| 3196 | JEMALLOC_ATTR(malloc) | ||
| 3197 | je_memalign(size_t alignment, size_t size) { | ||
| 3198 | void *ret; | ||
| 3199 | static_opts_t sopts; | ||
| 3200 | dynamic_opts_t dopts; | ||
| 3201 | |||
| 3202 | LOG("core.memalign.entry", "alignment: %zu, size: %zu\n", alignment, | ||
| 3203 | size); | ||
| 3204 | |||
| 3205 | static_opts_init(&sopts); | ||
| 3206 | dynamic_opts_init(&dopts); | ||
| 3207 | |||
| 3208 | sopts.min_alignment = 1; | ||
| 3209 | sopts.oom_string = | ||
| 3210 | "<jemalloc>: Error allocating aligned memory: out of memory\n"; | ||
| 3211 | sopts.invalid_alignment_string = | ||
| 3212 | "<jemalloc>: Error allocating aligned memory: invalid alignment\n"; | ||
| 3213 | sopts.null_out_result_on_error = true; | ||
| 3214 | |||
| 3215 | dopts.result = &ret; | ||
| 3216 | dopts.num_items = 1; | ||
| 3217 | dopts.item_size = size; | ||
| 3218 | dopts.alignment = alignment; | ||
| 3219 | |||
| 3220 | imalloc(&sopts, &dopts); | ||
| 3221 | if (sopts.slow) { | ||
| 3222 | uintptr_t args[3] = {alignment, size}; | ||
| 3223 | hook_invoke_alloc(hook_alloc_memalign, ret, (uintptr_t)ret, | ||
| 3224 | args); | ||
| 3225 | } | ||
| 3226 | |||
| 3227 | LOG("core.memalign.exit", "result: %p", ret); | ||
| 3228 | return ret; | ||
| 3229 | } | ||
| 3230 | #endif | ||
| 3231 | |||
| 3232 | #ifdef JEMALLOC_OVERRIDE_VALLOC | ||
| 3233 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 3234 | void JEMALLOC_NOTHROW * | ||
| 3235 | JEMALLOC_ATTR(malloc) | ||
| 3236 | je_valloc(size_t size) { | ||
| 3237 | void *ret; | ||
| 3238 | |||
| 3239 | static_opts_t sopts; | ||
| 3240 | dynamic_opts_t dopts; | ||
| 3241 | |||
| 3242 | LOG("core.valloc.entry", "size: %zu\n", size); | ||
| 3243 | |||
| 3244 | static_opts_init(&sopts); | ||
| 3245 | dynamic_opts_init(&dopts); | ||
| 3246 | |||
| 3247 | sopts.null_out_result_on_error = true; | ||
| 3248 | sopts.min_alignment = PAGE; | ||
| 3249 | sopts.oom_string = | ||
| 3250 | "<jemalloc>: Error allocating aligned memory: out of memory\n"; | ||
| 3251 | sopts.invalid_alignment_string = | ||
| 3252 | "<jemalloc>: Error allocating aligned memory: invalid alignment\n"; | ||
| 3253 | |||
| 3254 | dopts.result = &ret; | ||
| 3255 | dopts.num_items = 1; | ||
| 3256 | dopts.item_size = size; | ||
| 3257 | dopts.alignment = PAGE; | ||
| 3258 | |||
| 3259 | imalloc(&sopts, &dopts); | ||
| 3260 | if (sopts.slow) { | ||
| 3261 | uintptr_t args[3] = {size}; | ||
| 3262 | hook_invoke_alloc(hook_alloc_valloc, ret, (uintptr_t)ret, args); | ||
| 3263 | } | ||
| 3264 | |||
| 3265 | LOG("core.valloc.exit", "result: %p\n", ret); | ||
| 3266 | return ret; | ||
| 3267 | } | ||
| 3268 | #endif | ||
| 3269 | |||
| 3270 | #if defined(JEMALLOC_IS_MALLOC) && defined(JEMALLOC_GLIBC_MALLOC_HOOK) | ||
| 3271 | /* | ||
| 3272 | * glibc provides the RTLD_DEEPBIND flag for dlopen which can make it possible | ||
| 3273 | * to inconsistently reference libc's malloc(3)-compatible functions | ||
| 3274 | * (https://bugzilla.mozilla.org/show_bug.cgi?id=493541). | ||
| 3275 | * | ||
| 3276 | * These definitions interpose hooks in glibc. The functions are actually | ||
| 3277 | * passed an extra argument for the caller return address, which will be | ||
| 3278 | * ignored. | ||
| 3279 | */ | ||
| 3280 | #include <features.h> // defines __GLIBC__ if we are compiling against glibc | ||
| 3281 | |||
| 3282 | JEMALLOC_EXPORT void (*__free_hook)(void *ptr) = je_free; | ||
| 3283 | JEMALLOC_EXPORT void *(*__malloc_hook)(size_t size) = je_malloc; | ||
| 3284 | JEMALLOC_EXPORT void *(*__realloc_hook)(void *ptr, size_t size) = je_realloc; | ||
| 3285 | # ifdef JEMALLOC_GLIBC_MEMALIGN_HOOK | ||
| 3286 | JEMALLOC_EXPORT void *(*__memalign_hook)(size_t alignment, size_t size) = | ||
| 3287 | je_memalign; | ||
| 3288 | # endif | ||
| 3289 | |||
| 3290 | # ifdef __GLIBC__ | ||
| 3291 | /* | ||
| 3292 | * To enable static linking with glibc, the libc specific malloc interface must | ||
| 3293 | * be implemented also, so none of glibc's malloc.o functions are added to the | ||
| 3294 | * link. | ||
| 3295 | */ | ||
| 3296 | # define ALIAS(je_fn) __attribute__((alias (#je_fn), used)) | ||
| 3297 | /* To force macro expansion of je_ prefix before stringification. */ | ||
| 3298 | # define PREALIAS(je_fn) ALIAS(je_fn) | ||
| 3299 | # ifdef JEMALLOC_OVERRIDE___LIBC_CALLOC | ||
| 3300 | void *__libc_calloc(size_t n, size_t size) PREALIAS(je_calloc); | ||
| 3301 | # endif | ||
| 3302 | # ifdef JEMALLOC_OVERRIDE___LIBC_FREE | ||
| 3303 | void __libc_free(void* ptr) PREALIAS(je_free); | ||
| 3304 | # endif | ||
| 3305 | # ifdef JEMALLOC_OVERRIDE___LIBC_MALLOC | ||
| 3306 | void *__libc_malloc(size_t size) PREALIAS(je_malloc); | ||
| 3307 | # endif | ||
| 3308 | # ifdef JEMALLOC_OVERRIDE___LIBC_MEMALIGN | ||
| 3309 | void *__libc_memalign(size_t align, size_t s) PREALIAS(je_memalign); | ||
| 3310 | # endif | ||
| 3311 | # ifdef JEMALLOC_OVERRIDE___LIBC_REALLOC | ||
| 3312 | void *__libc_realloc(void* ptr, size_t size) PREALIAS(je_realloc); | ||
| 3313 | # endif | ||
| 3314 | # ifdef JEMALLOC_OVERRIDE___LIBC_VALLOC | ||
| 3315 | void *__libc_valloc(size_t size) PREALIAS(je_valloc); | ||
| 3316 | # endif | ||
| 3317 | # ifdef JEMALLOC_OVERRIDE___POSIX_MEMALIGN | ||
| 3318 | int __posix_memalign(void** r, size_t a, size_t s) PREALIAS(je_posix_memalign); | ||
| 3319 | # endif | ||
| 3320 | # undef PREALIAS | ||
| 3321 | # undef ALIAS | ||
| 3322 | # endif | ||
| 3323 | #endif | ||
| 3324 | |||
| 3325 | /* | ||
| 3326 | * End non-standard override functions. | ||
| 3327 | */ | ||
| 3328 | /******************************************************************************/ | ||
| 3329 | /* | ||
| 3330 | * Begin non-standard functions. | ||
| 3331 | */ | ||
| 3332 | |||
| 3333 | JEMALLOC_ALWAYS_INLINE unsigned | ||
| 3334 | mallocx_tcache_get(int flags) { | ||
| 3335 | if (likely((flags & MALLOCX_TCACHE_MASK) == 0)) { | ||
| 3336 | return TCACHE_IND_AUTOMATIC; | ||
| 3337 | } else if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE) { | ||
| 3338 | return TCACHE_IND_NONE; | ||
| 3339 | } else { | ||
| 3340 | return MALLOCX_TCACHE_GET(flags); | ||
| 3341 | } | ||
| 3342 | } | ||
| 3343 | |||
| 3344 | JEMALLOC_ALWAYS_INLINE unsigned | ||
| 3345 | mallocx_arena_get(int flags) { | ||
| 3346 | if (unlikely((flags & MALLOCX_ARENA_MASK) != 0)) { | ||
| 3347 | return MALLOCX_ARENA_GET(flags); | ||
| 3348 | } else { | ||
| 3349 | return ARENA_IND_AUTOMATIC; | ||
| 3350 | } | ||
| 3351 | } | ||
| 3352 | |||
| 3353 | #ifdef JEMALLOC_EXPERIMENTAL_SMALLOCX_API | ||
| 3354 | |||
| 3355 | #define JEMALLOC_SMALLOCX_CONCAT_HELPER(x, y) x ## y | ||
| 3356 | #define JEMALLOC_SMALLOCX_CONCAT_HELPER2(x, y) \ | ||
| 3357 | JEMALLOC_SMALLOCX_CONCAT_HELPER(x, y) | ||
| 3358 | |||
| 3359 | typedef struct { | ||
| 3360 | void *ptr; | ||
| 3361 | size_t size; | ||
| 3362 | } smallocx_return_t; | ||
| 3363 | |||
| 3364 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 3365 | smallocx_return_t JEMALLOC_NOTHROW | ||
| 3366 | /* | ||
| 3367 | * The attribute JEMALLOC_ATTR(malloc) cannot be used due to: | ||
| 3368 | * - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86488 | ||
| 3369 | */ | ||
| 3370 | JEMALLOC_SMALLOCX_CONCAT_HELPER2(je_smallocx_, JEMALLOC_VERSION_GID_IDENT) | ||
| 3371 | (size_t size, int flags) { | ||
| 3372 | /* | ||
| 3373 | * Note: the attribute JEMALLOC_ALLOC_SIZE(1) cannot be | ||
| 3374 | * used here because it makes writing beyond the `size` | ||
| 3375 | * of the `ptr` undefined behavior, but the objective | ||
| 3376 | * of this function is to allow writing beyond `size` | ||
| 3377 | * up to `smallocx_return_t::size`. | ||
| 3378 | */ | ||
| 3379 | smallocx_return_t ret; | ||
| 3380 | static_opts_t sopts; | ||
| 3381 | dynamic_opts_t dopts; | ||
| 3382 | |||
| 3383 | LOG("core.smallocx.entry", "size: %zu, flags: %d", size, flags); | ||
| 3384 | |||
| 3385 | static_opts_init(&sopts); | ||
| 3386 | dynamic_opts_init(&dopts); | ||
| 3387 | |||
| 3388 | sopts.assert_nonempty_alloc = true; | ||
| 3389 | sopts.null_out_result_on_error = true; | ||
| 3390 | sopts.oom_string = "<jemalloc>: Error in mallocx(): out of memory\n"; | ||
| 3391 | sopts.usize = true; | ||
| 3392 | |||
| 3393 | dopts.result = &ret.ptr; | ||
| 3394 | dopts.num_items = 1; | ||
| 3395 | dopts.item_size = size; | ||
| 3396 | if (unlikely(flags != 0)) { | ||
| 3397 | dopts.alignment = MALLOCX_ALIGN_GET(flags); | ||
| 3398 | dopts.zero = MALLOCX_ZERO_GET(flags); | ||
| 3399 | dopts.tcache_ind = mallocx_tcache_get(flags); | ||
| 3400 | dopts.arena_ind = mallocx_arena_get(flags); | ||
| 3401 | } | ||
| 3402 | |||
| 3403 | imalloc(&sopts, &dopts); | ||
| 3404 | assert(dopts.usize == je_nallocx(size, flags)); | ||
| 3405 | ret.size = dopts.usize; | ||
| 3406 | |||
| 3407 | LOG("core.smallocx.exit", "result: %p, size: %zu", ret.ptr, ret.size); | ||
| 3408 | return ret; | ||
| 3409 | } | ||
| 3410 | #undef JEMALLOC_SMALLOCX_CONCAT_HELPER | ||
| 3411 | #undef JEMALLOC_SMALLOCX_CONCAT_HELPER2 | ||
| 3412 | #endif | ||
| 3413 | |||
| 3414 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 3415 | void JEMALLOC_NOTHROW * | ||
| 3416 | JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1) | ||
| 3417 | je_mallocx(size_t size, int flags) { | ||
| 3418 | void *ret; | ||
| 3419 | static_opts_t sopts; | ||
| 3420 | dynamic_opts_t dopts; | ||
| 3421 | |||
| 3422 | LOG("core.mallocx.entry", "size: %zu, flags: %d", size, flags); | ||
| 3423 | |||
| 3424 | static_opts_init(&sopts); | ||
| 3425 | dynamic_opts_init(&dopts); | ||
| 3426 | |||
| 3427 | sopts.assert_nonempty_alloc = true; | ||
| 3428 | sopts.null_out_result_on_error = true; | ||
| 3429 | sopts.oom_string = "<jemalloc>: Error in mallocx(): out of memory\n"; | ||
| 3430 | |||
| 3431 | dopts.result = &ret; | ||
| 3432 | dopts.num_items = 1; | ||
| 3433 | dopts.item_size = size; | ||
| 3434 | if (unlikely(flags != 0)) { | ||
| 3435 | dopts.alignment = MALLOCX_ALIGN_GET(flags); | ||
| 3436 | dopts.zero = MALLOCX_ZERO_GET(flags); | ||
| 3437 | dopts.tcache_ind = mallocx_tcache_get(flags); | ||
| 3438 | dopts.arena_ind = mallocx_arena_get(flags); | ||
| 3439 | } | ||
| 3440 | |||
| 3441 | imalloc(&sopts, &dopts); | ||
| 3442 | if (sopts.slow) { | ||
| 3443 | uintptr_t args[3] = {size, flags}; | ||
| 3444 | hook_invoke_alloc(hook_alloc_mallocx, ret, (uintptr_t)ret, | ||
| 3445 | args); | ||
| 3446 | } | ||
| 3447 | |||
| 3448 | LOG("core.mallocx.exit", "result: %p", ret); | ||
| 3449 | return ret; | ||
| 3450 | } | ||
| 3451 | |||
| 3452 | static void * | ||
| 3453 | irallocx_prof_sample(tsdn_t *tsdn, void *old_ptr, size_t old_usize, | ||
| 3454 | size_t usize, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena, | ||
| 3455 | prof_tctx_t *tctx, hook_ralloc_args_t *hook_args) { | ||
| 3456 | void *p; | ||
| 3457 | |||
| 3458 | if (tctx == NULL) { | ||
| 3459 | return NULL; | ||
| 3460 | } | ||
| 3461 | |||
| 3462 | alignment = prof_sample_align(alignment); | ||
| 3463 | if (usize <= SC_SMALL_MAXCLASS) { | ||
| 3464 | p = iralloct(tsdn, old_ptr, old_usize, | ||
| 3465 | SC_LARGE_MINCLASS, alignment, zero, tcache, | ||
| 3466 | arena, hook_args); | ||
| 3467 | if (p == NULL) { | ||
| 3468 | return NULL; | ||
| 3469 | } | ||
| 3470 | arena_prof_promote(tsdn, p, usize); | ||
| 3471 | } else { | ||
| 3472 | p = iralloct(tsdn, old_ptr, old_usize, usize, alignment, zero, | ||
| 3473 | tcache, arena, hook_args); | ||
| 3474 | } | ||
| 3475 | assert(prof_sample_aligned(p)); | ||
| 3476 | |||
| 3477 | return p; | ||
| 3478 | } | ||
| 3479 | |||
| 3480 | JEMALLOC_ALWAYS_INLINE void * | ||
| 3481 | irallocx_prof(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t size, | ||
| 3482 | size_t alignment, size_t usize, bool zero, tcache_t *tcache, | ||
| 3483 | arena_t *arena, emap_alloc_ctx_t *alloc_ctx, | ||
| 3484 | hook_ralloc_args_t *hook_args) { | ||
| 3485 | prof_info_t old_prof_info; | ||
| 3486 | prof_info_get_and_reset_recent(tsd, old_ptr, alloc_ctx, &old_prof_info); | ||
| 3487 | bool prof_active = prof_active_get_unlocked(); | ||
| 3488 | bool sample_event = te_prof_sample_event_lookahead(tsd, usize); | ||
| 3489 | prof_tctx_t *tctx = prof_alloc_prep(tsd, prof_active, sample_event); | ||
| 3490 | void *p; | ||
| 3491 | if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) { | ||
| 3492 | p = irallocx_prof_sample(tsd_tsdn(tsd), old_ptr, old_usize, | ||
| 3493 | usize, alignment, zero, tcache, arena, tctx, hook_args); | ||
| 3494 | } else { | ||
| 3495 | p = iralloct(tsd_tsdn(tsd), old_ptr, old_usize, size, alignment, | ||
| 3496 | zero, tcache, arena, hook_args); | ||
| 3497 | } | ||
| 3498 | if (unlikely(p == NULL)) { | ||
| 3499 | prof_alloc_rollback(tsd, tctx); | ||
| 3500 | return NULL; | ||
| 3501 | } | ||
| 3502 | assert(usize == isalloc(tsd_tsdn(tsd), p)); | ||
| 3503 | prof_realloc(tsd, p, size, usize, tctx, prof_active, old_ptr, | ||
| 3504 | old_usize, &old_prof_info, sample_event); | ||
| 3505 | |||
| 3506 | return p; | ||
| 3507 | } | ||
| 3508 | |||
| 3509 | static void * | ||
| 3510 | do_rallocx(void *ptr, size_t size, int flags, bool is_realloc, size_t *old_usable_size, size_t *new_usable_size) { | ||
| 3511 | void *p; | ||
| 3512 | tsd_t *tsd; | ||
| 3513 | size_t usize; | ||
| 3514 | size_t old_usize; | ||
| 3515 | size_t alignment = MALLOCX_ALIGN_GET(flags); | ||
| 3516 | arena_t *arena; | ||
| 3517 | |||
| 3518 | assert(ptr != NULL); | ||
| 3519 | assert(size != 0); | ||
| 3520 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 3521 | tsd = tsd_fetch(); | ||
| 3522 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3523 | |||
| 3524 | bool zero = zero_get(MALLOCX_ZERO_GET(flags), /* slow */ true); | ||
| 3525 | |||
| 3526 | unsigned arena_ind = mallocx_arena_get(flags); | ||
| 3527 | if (arena_get_from_ind(tsd, arena_ind, &arena)) { | ||
| 3528 | goto label_oom; | ||
| 3529 | } | ||
| 3530 | |||
| 3531 | unsigned tcache_ind = mallocx_tcache_get(flags); | ||
| 3532 | tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, | ||
| 3533 | /* slow */ true, /* is_alloc */ true); | ||
| 3534 | |||
| 3535 | emap_alloc_ctx_t alloc_ctx; | ||
| 3536 | emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, | ||
| 3537 | &alloc_ctx); | ||
| 3538 | assert(alloc_ctx.szind != SC_NSIZES); | ||
| 3539 | old_usize = sz_index2size(alloc_ctx.szind); | ||
| 3540 | assert(old_usize == isalloc(tsd_tsdn(tsd), ptr)); | ||
| 3541 | if (aligned_usize_get(size, alignment, &usize, NULL, false)) { | ||
| 3542 | goto label_oom; | ||
| 3543 | } | ||
| 3544 | |||
| 3545 | hook_ralloc_args_t hook_args = {is_realloc, {(uintptr_t)ptr, size, | ||
| 3546 | flags, 0}}; | ||
| 3547 | if (config_prof && opt_prof) { | ||
| 3548 | p = irallocx_prof(tsd, ptr, old_usize, size, alignment, usize, | ||
| 3549 | zero, tcache, arena, &alloc_ctx, &hook_args); | ||
| 3550 | if (unlikely(p == NULL)) { | ||
| 3551 | goto label_oom; | ||
| 3552 | } | ||
| 3553 | } else { | ||
| 3554 | p = iralloct(tsd_tsdn(tsd), ptr, old_usize, size, alignment, | ||
| 3555 | zero, tcache, arena, &hook_args); | ||
| 3556 | if (unlikely(p == NULL)) { | ||
| 3557 | goto label_oom; | ||
| 3558 | } | ||
| 3559 | assert(usize == isalloc(tsd_tsdn(tsd), p)); | ||
| 3560 | } | ||
| 3561 | assert(alignment == 0 || ((uintptr_t)p & (alignment - 1)) == ZU(0)); | ||
| 3562 | thread_alloc_event(tsd, usize); | ||
| 3563 | thread_dalloc_event(tsd, old_usize); | ||
| 3564 | |||
| 3565 | UTRACE(ptr, size, p); | ||
| 3566 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3567 | |||
| 3568 | if (config_fill && unlikely(opt_junk_alloc) && usize > old_usize | ||
| 3569 | && !zero) { | ||
| 3570 | size_t excess_len = usize - old_usize; | ||
| 3571 | void *excess_start = (void *)((uintptr_t)p + old_usize); | ||
| 3572 | junk_alloc_callback(excess_start, excess_len); | ||
| 3573 | } | ||
| 3574 | |||
| 3575 | if (old_usable_size) *old_usable_size = old_usize; | ||
| 3576 | if (new_usable_size) *new_usable_size = usize; | ||
| 3577 | return p; | ||
| 3578 | label_oom: | ||
| 3579 | if (config_xmalloc && unlikely(opt_xmalloc)) { | ||
| 3580 | malloc_write("<jemalloc>: Error in rallocx(): out of memory\n"); | ||
| 3581 | abort(); | ||
| 3582 | } | ||
| 3583 | UTRACE(ptr, size, 0); | ||
| 3584 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3585 | |||
| 3586 | return NULL; | ||
| 3587 | } | ||
| 3588 | |||
| 3589 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 3590 | void JEMALLOC_NOTHROW * | ||
| 3591 | JEMALLOC_ALLOC_SIZE(2) | ||
| 3592 | je_rallocx(void *ptr, size_t size, int flags) { | ||
| 3593 | LOG("core.rallocx.entry", "ptr: %p, size: %zu, flags: %d", ptr, | ||
| 3594 | size, flags); | ||
| 3595 | void *ret = do_rallocx(ptr, size, flags, false, NULL, NULL); | ||
| 3596 | LOG("core.rallocx.exit", "result: %p", ret); | ||
| 3597 | return ret; | ||
| 3598 | } | ||
| 3599 | |||
| 3600 | static void * | ||
| 3601 | do_realloc_nonnull_zero(void *ptr, size_t *old_usize, size_t *new_usize) { | ||
| 3602 | if (config_stats) { | ||
| 3603 | atomic_fetch_add_zu(&zero_realloc_count, 1, ATOMIC_RELAXED); | ||
| 3604 | } | ||
| 3605 | if (opt_zero_realloc_action == zero_realloc_action_alloc) { | ||
| 3606 | /* | ||
| 3607 | * The user might have gotten an alloc setting while expecting a | ||
| 3608 | * free setting. If that's the case, we at least try to | ||
| 3609 | * reduce the harm, and turn off the tcache while allocating, so | ||
| 3610 | * that we'll get a true first fit. | ||
| 3611 | */ | ||
| 3612 | return do_rallocx(ptr, 1, MALLOCX_TCACHE_NONE, true, old_usize, new_usize); | ||
| 3613 | } else if (opt_zero_realloc_action == zero_realloc_action_free) { | ||
| 3614 | UTRACE(ptr, 0, 0); | ||
| 3615 | tsd_t *tsd = tsd_fetch(); | ||
| 3616 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3617 | |||
| 3618 | tcache_t *tcache = tcache_get_from_ind(tsd, | ||
| 3619 | TCACHE_IND_AUTOMATIC, /* slow */ true, | ||
| 3620 | /* is_alloc */ false); | ||
| 3621 | uintptr_t args[3] = {(uintptr_t)ptr, 0}; | ||
| 3622 | hook_invoke_dalloc(hook_dalloc_realloc, ptr, args); | ||
| 3623 | size_t usize; | ||
| 3624 | ifree(tsd, ptr, tcache, true, &usize); | ||
| 3625 | if (old_usize) *old_usize = usize; | ||
| 3626 | if (new_usize) *new_usize = 0; | ||
| 3627 | |||
| 3628 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3629 | return NULL; | ||
| 3630 | } else { | ||
| 3631 | safety_check_fail("Called realloc(non-null-ptr, 0) with " | ||
| 3632 | "zero_realloc:abort set\n"); | ||
| 3633 | /* In real code, this will never run; the safety check failure | ||
| 3634 | * will call abort. In the unit test, we just want to bail out | ||
| 3635 | * without corrupting internal state that the test needs to | ||
| 3636 | * finish. | ||
| 3637 | */ | ||
| 3638 | return NULL; | ||
| 3639 | } | ||
| 3640 | } | ||
| 3641 | |||
| 3642 | static inline void *je_realloc_internal(void *ptr, size_t size, size_t *old_usize, size_t *new_usize) { | ||
| 3643 | LOG("core.realloc.entry", "ptr: %p, size: %zu\n", ptr, size); | ||
| 3644 | |||
| 3645 | if (likely(ptr != NULL && size != 0)) { | ||
| 3646 | void *ret = do_rallocx(ptr, size, 0, true, old_usize, new_usize); | ||
| 3647 | LOG("core.realloc.exit", "result: %p", ret); | ||
| 3648 | return ret; | ||
| 3649 | } else if (ptr != NULL && size == 0) { | ||
| 3650 | void *ret = do_realloc_nonnull_zero(ptr, old_usize, new_usize); | ||
| 3651 | LOG("core.realloc.exit", "result: %p", ret); | ||
| 3652 | return ret; | ||
| 3653 | } else { | ||
| 3654 | /* realloc(NULL, size) is equivalent to malloc(size). */ | ||
| 3655 | void *ret; | ||
| 3656 | |||
| 3657 | static_opts_t sopts; | ||
| 3658 | dynamic_opts_t dopts; | ||
| 3659 | |||
| 3660 | static_opts_init(&sopts); | ||
| 3661 | dynamic_opts_init(&dopts); | ||
| 3662 | |||
| 3663 | sopts.null_out_result_on_error = true; | ||
| 3664 | sopts.set_errno_on_error = true; | ||
| 3665 | sopts.oom_string = | ||
| 3666 | "<jemalloc>: Error in realloc(): out of memory\n"; | ||
| 3667 | |||
| 3668 | dopts.result = &ret; | ||
| 3669 | dopts.num_items = 1; | ||
| 3670 | dopts.item_size = size; | ||
| 3671 | |||
| 3672 | imalloc(&sopts, &dopts); | ||
| 3673 | if (sopts.slow) { | ||
| 3674 | uintptr_t args[3] = {(uintptr_t)ptr, size}; | ||
| 3675 | hook_invoke_alloc(hook_alloc_realloc, ret, | ||
| 3676 | (uintptr_t)ret, args); | ||
| 3677 | } | ||
| 3678 | LOG("core.realloc.exit", "result: %p", ret); | ||
| 3679 | if (old_usize) *old_usize = 0; | ||
| 3680 | if (new_usize) *new_usize = dopts.usize; | ||
| 3681 | return ret; | ||
| 3682 | } | ||
| 3683 | } | ||
| 3684 | |||
| 3685 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 3686 | void JEMALLOC_NOTHROW * | ||
| 3687 | JEMALLOC_ALLOC_SIZE(2) | ||
| 3688 | je_realloc(void *ptr, size_t size) { | ||
| 3689 | return je_realloc_internal(ptr, size, NULL, NULL); | ||
| 3690 | } | ||
| 3691 | |||
| 3692 | JEMALLOC_ALWAYS_INLINE size_t | ||
| 3693 | ixallocx_helper(tsdn_t *tsdn, void *ptr, size_t old_usize, size_t size, | ||
| 3694 | size_t extra, size_t alignment, bool zero) { | ||
| 3695 | size_t newsize; | ||
| 3696 | |||
| 3697 | if (ixalloc(tsdn, ptr, old_usize, size, extra, alignment, zero, | ||
| 3698 | &newsize)) { | ||
| 3699 | return old_usize; | ||
| 3700 | } | ||
| 3701 | |||
| 3702 | return newsize; | ||
| 3703 | } | ||
| 3704 | |||
| 3705 | static size_t | ||
| 3706 | ixallocx_prof_sample(tsdn_t *tsdn, void *ptr, size_t old_usize, size_t size, | ||
| 3707 | size_t extra, size_t alignment, bool zero, prof_tctx_t *tctx) { | ||
| 3708 | /* Sampled allocation needs to be page aligned. */ | ||
| 3709 | if (tctx == NULL || !prof_sample_aligned(ptr)) { | ||
| 3710 | return old_usize; | ||
| 3711 | } | ||
| 3712 | |||
| 3713 | return ixallocx_helper(tsdn, ptr, old_usize, size, extra, alignment, | ||
| 3714 | zero); | ||
| 3715 | } | ||
| 3716 | |||
| 3717 | JEMALLOC_ALWAYS_INLINE size_t | ||
| 3718 | ixallocx_prof(tsd_t *tsd, void *ptr, size_t old_usize, size_t size, | ||
| 3719 | size_t extra, size_t alignment, bool zero, emap_alloc_ctx_t *alloc_ctx) { | ||
| 3720 | /* | ||
| 3721 | * old_prof_info is only used for asserting that the profiling info | ||
| 3722 | * isn't changed by the ixalloc() call. | ||
| 3723 | */ | ||
| 3724 | prof_info_t old_prof_info; | ||
| 3725 | prof_info_get(tsd, ptr, alloc_ctx, &old_prof_info); | ||
| 3726 | |||
| 3727 | /* | ||
| 3728 | * usize isn't knowable before ixalloc() returns when extra is non-zero. | ||
| 3729 | * Therefore, compute its maximum possible value and use that in | ||
| 3730 | * prof_alloc_prep() to decide whether to capture a backtrace. | ||
| 3731 | * prof_realloc() will use the actual usize to decide whether to sample. | ||
| 3732 | */ | ||
| 3733 | size_t usize_max; | ||
| 3734 | if (aligned_usize_get(size + extra, alignment, &usize_max, NULL, | ||
| 3735 | false)) { | ||
| 3736 | /* | ||
| 3737 | * usize_max is out of range, and chances are that allocation | ||
| 3738 | * will fail, but use the maximum possible value and carry on | ||
| 3739 | * with prof_alloc_prep(), just in case allocation succeeds. | ||
| 3740 | */ | ||
| 3741 | usize_max = SC_LARGE_MAXCLASS; | ||
| 3742 | } | ||
| 3743 | bool prof_active = prof_active_get_unlocked(); | ||
| 3744 | bool sample_event = te_prof_sample_event_lookahead(tsd, usize_max); | ||
| 3745 | prof_tctx_t *tctx = prof_alloc_prep(tsd, prof_active, sample_event); | ||
| 3746 | |||
| 3747 | size_t usize; | ||
| 3748 | if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) { | ||
| 3749 | usize = ixallocx_prof_sample(tsd_tsdn(tsd), ptr, old_usize, | ||
| 3750 | size, extra, alignment, zero, tctx); | ||
| 3751 | } else { | ||
| 3752 | usize = ixallocx_helper(tsd_tsdn(tsd), ptr, old_usize, size, | ||
| 3753 | extra, alignment, zero); | ||
| 3754 | } | ||
| 3755 | |||
| 3756 | /* | ||
| 3757 | * At this point we can still safely get the original profiling | ||
| 3758 | * information associated with the ptr, because (a) the edata_t object | ||
| 3759 | * associated with the ptr still lives and (b) the profiling info | ||
| 3760 | * fields are not touched. "(a)" is asserted in the outer je_xallocx() | ||
| 3761 | * function, and "(b)" is indirectly verified below by checking that | ||
| 3762 | * the alloc_tctx field is unchanged. | ||
| 3763 | */ | ||
| 3764 | prof_info_t prof_info; | ||
| 3765 | if (usize == old_usize) { | ||
| 3766 | prof_info_get(tsd, ptr, alloc_ctx, &prof_info); | ||
| 3767 | prof_alloc_rollback(tsd, tctx); | ||
| 3768 | } else { | ||
| 3769 | prof_info_get_and_reset_recent(tsd, ptr, alloc_ctx, &prof_info); | ||
| 3770 | assert(usize <= usize_max); | ||
| 3771 | sample_event = te_prof_sample_event_lookahead(tsd, usize); | ||
| 3772 | prof_realloc(tsd, ptr, size, usize, tctx, prof_active, ptr, | ||
| 3773 | old_usize, &prof_info, sample_event); | ||
| 3774 | } | ||
| 3775 | |||
| 3776 | assert(old_prof_info.alloc_tctx == prof_info.alloc_tctx); | ||
| 3777 | return usize; | ||
| 3778 | } | ||
| 3779 | |||
| 3780 | JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW | ||
| 3781 | je_xallocx(void *ptr, size_t size, size_t extra, int flags) { | ||
| 3782 | tsd_t *tsd; | ||
| 3783 | size_t usize, old_usize; | ||
| 3784 | size_t alignment = MALLOCX_ALIGN_GET(flags); | ||
| 3785 | bool zero = zero_get(MALLOCX_ZERO_GET(flags), /* slow */ true); | ||
| 3786 | |||
| 3787 | LOG("core.xallocx.entry", "ptr: %p, size: %zu, extra: %zu, " | ||
| 3788 | "flags: %d", ptr, size, extra, flags); | ||
| 3789 | |||
| 3790 | assert(ptr != NULL); | ||
| 3791 | assert(size != 0); | ||
| 3792 | assert(SIZE_T_MAX - size >= extra); | ||
| 3793 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 3794 | tsd = tsd_fetch(); | ||
| 3795 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3796 | |||
| 3797 | /* | ||
| 3798 | * old_edata is only for verifying that xallocx() keeps the edata_t | ||
| 3799 | * object associated with the ptr (though the content of the edata_t | ||
| 3800 | * object can be changed). | ||
| 3801 | */ | ||
| 3802 | edata_t *old_edata = emap_edata_lookup(tsd_tsdn(tsd), | ||
| 3803 | &arena_emap_global, ptr); | ||
| 3804 | |||
| 3805 | emap_alloc_ctx_t alloc_ctx; | ||
| 3806 | emap_alloc_ctx_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr, | ||
| 3807 | &alloc_ctx); | ||
| 3808 | assert(alloc_ctx.szind != SC_NSIZES); | ||
| 3809 | old_usize = sz_index2size(alloc_ctx.szind); | ||
| 3810 | assert(old_usize == isalloc(tsd_tsdn(tsd), ptr)); | ||
| 3811 | /* | ||
| 3812 | * The API explicitly absolves itself of protecting against (size + | ||
| 3813 | * extra) numerical overflow, but we may need to clamp extra to avoid | ||
| 3814 | * exceeding SC_LARGE_MAXCLASS. | ||
| 3815 | * | ||
| 3816 | * Ordinarily, size limit checking is handled deeper down, but here we | ||
| 3817 | * have to check as part of (size + extra) clamping, since we need the | ||
| 3818 | * clamped value in the above helper functions. | ||
| 3819 | */ | ||
| 3820 | if (unlikely(size > SC_LARGE_MAXCLASS)) { | ||
| 3821 | usize = old_usize; | ||
| 3822 | goto label_not_resized; | ||
| 3823 | } | ||
| 3824 | if (unlikely(SC_LARGE_MAXCLASS - size < extra)) { | ||
| 3825 | extra = SC_LARGE_MAXCLASS - size; | ||
| 3826 | } | ||
| 3827 | |||
| 3828 | if (config_prof && opt_prof) { | ||
| 3829 | usize = ixallocx_prof(tsd, ptr, old_usize, size, extra, | ||
| 3830 | alignment, zero, &alloc_ctx); | ||
| 3831 | } else { | ||
| 3832 | usize = ixallocx_helper(tsd_tsdn(tsd), ptr, old_usize, size, | ||
| 3833 | extra, alignment, zero); | ||
| 3834 | } | ||
| 3835 | |||
| 3836 | /* | ||
| 3837 | * xallocx() should keep using the same edata_t object (though its | ||
| 3838 | * content can be changed). | ||
| 3839 | */ | ||
| 3840 | assert(emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, ptr) | ||
| 3841 | == old_edata); | ||
| 3842 | |||
| 3843 | if (unlikely(usize == old_usize)) { | ||
| 3844 | goto label_not_resized; | ||
| 3845 | } | ||
| 3846 | thread_alloc_event(tsd, usize); | ||
| 3847 | thread_dalloc_event(tsd, old_usize); | ||
| 3848 | |||
| 3849 | if (config_fill && unlikely(opt_junk_alloc) && usize > old_usize && | ||
| 3850 | !zero) { | ||
| 3851 | size_t excess_len = usize - old_usize; | ||
| 3852 | void *excess_start = (void *)((uintptr_t)ptr + old_usize); | ||
| 3853 | junk_alloc_callback(excess_start, excess_len); | ||
| 3854 | } | ||
| 3855 | label_not_resized: | ||
| 3856 | if (unlikely(!tsd_fast(tsd))) { | ||
| 3857 | uintptr_t args[4] = {(uintptr_t)ptr, size, extra, flags}; | ||
| 3858 | hook_invoke_expand(hook_expand_xallocx, ptr, old_usize, | ||
| 3859 | usize, (uintptr_t)usize, args); | ||
| 3860 | } | ||
| 3861 | |||
| 3862 | UTRACE(ptr, size, ptr); | ||
| 3863 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3864 | |||
| 3865 | LOG("core.xallocx.exit", "result: %zu", usize); | ||
| 3866 | return usize; | ||
| 3867 | } | ||
| 3868 | |||
| 3869 | JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW | ||
| 3870 | JEMALLOC_ATTR(pure) | ||
| 3871 | je_sallocx(const void *ptr, int flags) { | ||
| 3872 | size_t usize; | ||
| 3873 | tsdn_t *tsdn; | ||
| 3874 | |||
| 3875 | LOG("core.sallocx.entry", "ptr: %p, flags: %d", ptr, flags); | ||
| 3876 | |||
| 3877 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 3878 | assert(ptr != NULL); | ||
| 3879 | |||
| 3880 | tsdn = tsdn_fetch(); | ||
| 3881 | check_entry_exit_locking(tsdn); | ||
| 3882 | |||
| 3883 | if (config_debug || force_ivsalloc) { | ||
| 3884 | usize = ivsalloc(tsdn, ptr); | ||
| 3885 | assert(force_ivsalloc || usize != 0); | ||
| 3886 | } else { | ||
| 3887 | usize = isalloc(tsdn, ptr); | ||
| 3888 | } | ||
| 3889 | |||
| 3890 | check_entry_exit_locking(tsdn); | ||
| 3891 | |||
| 3892 | LOG("core.sallocx.exit", "result: %zu", usize); | ||
| 3893 | return usize; | ||
| 3894 | } | ||
| 3895 | |||
| 3896 | JEMALLOC_EXPORT void JEMALLOC_NOTHROW | ||
| 3897 | je_dallocx(void *ptr, int flags) { | ||
| 3898 | LOG("core.dallocx.entry", "ptr: %p, flags: %d", ptr, flags); | ||
| 3899 | |||
| 3900 | assert(ptr != NULL); | ||
| 3901 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 3902 | |||
| 3903 | tsd_t *tsd = tsd_fetch_min(); | ||
| 3904 | bool fast = tsd_fast(tsd); | ||
| 3905 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3906 | |||
| 3907 | unsigned tcache_ind = mallocx_tcache_get(flags); | ||
| 3908 | tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, !fast, | ||
| 3909 | /* is_alloc */ false); | ||
| 3910 | |||
| 3911 | UTRACE(ptr, 0, 0); | ||
| 3912 | if (likely(fast)) { | ||
| 3913 | tsd_assert_fast(tsd); | ||
| 3914 | ifree(tsd, ptr, tcache, false, NULL); | ||
| 3915 | } else { | ||
| 3916 | uintptr_t args_raw[3] = {(uintptr_t)ptr, flags}; | ||
| 3917 | hook_invoke_dalloc(hook_dalloc_dallocx, ptr, args_raw); | ||
| 3918 | ifree(tsd, ptr, tcache, true, NULL); | ||
| 3919 | } | ||
| 3920 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3921 | |||
| 3922 | LOG("core.dallocx.exit", ""); | ||
| 3923 | } | ||
| 3924 | |||
| 3925 | JEMALLOC_ALWAYS_INLINE size_t | ||
| 3926 | inallocx(tsdn_t *tsdn, size_t size, int flags) { | ||
| 3927 | check_entry_exit_locking(tsdn); | ||
| 3928 | size_t usize; | ||
| 3929 | /* In case of out of range, let the user see it rather than fail. */ | ||
| 3930 | aligned_usize_get(size, MALLOCX_ALIGN_GET(flags), &usize, NULL, false); | ||
| 3931 | check_entry_exit_locking(tsdn); | ||
| 3932 | return usize; | ||
| 3933 | } | ||
| 3934 | |||
| 3935 | JEMALLOC_NOINLINE void | ||
| 3936 | sdallocx_default(void *ptr, size_t size, int flags) { | ||
| 3937 | assert(ptr != NULL); | ||
| 3938 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 3939 | |||
| 3940 | tsd_t *tsd = tsd_fetch_min(); | ||
| 3941 | bool fast = tsd_fast(tsd); | ||
| 3942 | size_t usize = inallocx(tsd_tsdn(tsd), size, flags); | ||
| 3943 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3944 | |||
| 3945 | unsigned tcache_ind = mallocx_tcache_get(flags); | ||
| 3946 | tcache_t *tcache = tcache_get_from_ind(tsd, tcache_ind, !fast, | ||
| 3947 | /* is_alloc */ false); | ||
| 3948 | |||
| 3949 | UTRACE(ptr, 0, 0); | ||
| 3950 | if (likely(fast)) { | ||
| 3951 | tsd_assert_fast(tsd); | ||
| 3952 | isfree(tsd, ptr, usize, tcache, false); | ||
| 3953 | } else { | ||
| 3954 | uintptr_t args_raw[3] = {(uintptr_t)ptr, size, flags}; | ||
| 3955 | hook_invoke_dalloc(hook_dalloc_sdallocx, ptr, args_raw); | ||
| 3956 | isfree(tsd, ptr, usize, tcache, true); | ||
| 3957 | } | ||
| 3958 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 3959 | } | ||
| 3960 | |||
| 3961 | JEMALLOC_EXPORT void JEMALLOC_NOTHROW | ||
| 3962 | je_sdallocx(void *ptr, size_t size, int flags) { | ||
| 3963 | LOG("core.sdallocx.entry", "ptr: %p, size: %zu, flags: %d", ptr, | ||
| 3964 | size, flags); | ||
| 3965 | |||
| 3966 | if (flags != 0 || !free_fastpath(ptr, size, true, NULL)) { | ||
| 3967 | sdallocx_default(ptr, size, flags); | ||
| 3968 | } | ||
| 3969 | |||
| 3970 | LOG("core.sdallocx.exit", ""); | ||
| 3971 | } | ||
| 3972 | |||
| 3973 | void JEMALLOC_NOTHROW | ||
| 3974 | je_sdallocx_noflags(void *ptr, size_t size) { | ||
| 3975 | LOG("core.sdallocx.entry", "ptr: %p, size: %zu, flags: 0", ptr, | ||
| 3976 | size); | ||
| 3977 | |||
| 3978 | if (!free_fastpath(ptr, size, true, NULL)) { | ||
| 3979 | sdallocx_default(ptr, size, 0); | ||
| 3980 | } | ||
| 3981 | |||
| 3982 | LOG("core.sdallocx.exit", ""); | ||
| 3983 | } | ||
| 3984 | |||
| 3985 | JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW | ||
| 3986 | JEMALLOC_ATTR(pure) | ||
| 3987 | je_nallocx(size_t size, int flags) { | ||
| 3988 | size_t usize; | ||
| 3989 | tsdn_t *tsdn; | ||
| 3990 | |||
| 3991 | assert(size != 0); | ||
| 3992 | |||
| 3993 | if (unlikely(malloc_init())) { | ||
| 3994 | LOG("core.nallocx.exit", "result: %zu", ZU(0)); | ||
| 3995 | return 0; | ||
| 3996 | } | ||
| 3997 | |||
| 3998 | tsdn = tsdn_fetch(); | ||
| 3999 | check_entry_exit_locking(tsdn); | ||
| 4000 | |||
| 4001 | usize = inallocx(tsdn, size, flags); | ||
| 4002 | if (unlikely(usize > SC_LARGE_MAXCLASS)) { | ||
| 4003 | LOG("core.nallocx.exit", "result: %zu", ZU(0)); | ||
| 4004 | return 0; | ||
| 4005 | } | ||
| 4006 | |||
| 4007 | check_entry_exit_locking(tsdn); | ||
| 4008 | LOG("core.nallocx.exit", "result: %zu", usize); | ||
| 4009 | return usize; | ||
| 4010 | } | ||
| 4011 | |||
| 4012 | JEMALLOC_EXPORT int JEMALLOC_NOTHROW | ||
| 4013 | je_mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, | ||
| 4014 | size_t newlen) { | ||
| 4015 | int ret; | ||
| 4016 | tsd_t *tsd; | ||
| 4017 | |||
| 4018 | LOG("core.mallctl.entry", "name: %s", name); | ||
| 4019 | |||
| 4020 | if (unlikely(malloc_init())) { | ||
| 4021 | LOG("core.mallctl.exit", "result: %d", EAGAIN); | ||
| 4022 | return EAGAIN; | ||
| 4023 | } | ||
| 4024 | |||
| 4025 | tsd = tsd_fetch(); | ||
| 4026 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4027 | ret = ctl_byname(tsd, name, oldp, oldlenp, newp, newlen); | ||
| 4028 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4029 | |||
| 4030 | LOG("core.mallctl.exit", "result: %d", ret); | ||
| 4031 | return ret; | ||
| 4032 | } | ||
| 4033 | |||
| 4034 | JEMALLOC_EXPORT int JEMALLOC_NOTHROW | ||
| 4035 | je_mallctlnametomib(const char *name, size_t *mibp, size_t *miblenp) { | ||
| 4036 | int ret; | ||
| 4037 | |||
| 4038 | LOG("core.mallctlnametomib.entry", "name: %s", name); | ||
| 4039 | |||
| 4040 | if (unlikely(malloc_init())) { | ||
| 4041 | LOG("core.mallctlnametomib.exit", "result: %d", EAGAIN); | ||
| 4042 | return EAGAIN; | ||
| 4043 | } | ||
| 4044 | |||
| 4045 | tsd_t *tsd = tsd_fetch(); | ||
| 4046 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4047 | ret = ctl_nametomib(tsd, name, mibp, miblenp); | ||
| 4048 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4049 | |||
| 4050 | LOG("core.mallctlnametomib.exit", "result: %d", ret); | ||
| 4051 | return ret; | ||
| 4052 | } | ||
| 4053 | |||
| 4054 | JEMALLOC_EXPORT int JEMALLOC_NOTHROW | ||
| 4055 | je_mallctlbymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, | ||
| 4056 | void *newp, size_t newlen) { | ||
| 4057 | int ret; | ||
| 4058 | tsd_t *tsd; | ||
| 4059 | |||
| 4060 | LOG("core.mallctlbymib.entry", ""); | ||
| 4061 | |||
| 4062 | if (unlikely(malloc_init())) { | ||
| 4063 | LOG("core.mallctlbymib.exit", "result: %d", EAGAIN); | ||
| 4064 | return EAGAIN; | ||
| 4065 | } | ||
| 4066 | |||
| 4067 | tsd = tsd_fetch(); | ||
| 4068 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4069 | ret = ctl_bymib(tsd, mib, miblen, oldp, oldlenp, newp, newlen); | ||
| 4070 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4071 | LOG("core.mallctlbymib.exit", "result: %d", ret); | ||
| 4072 | return ret; | ||
| 4073 | } | ||
| 4074 | |||
| 4075 | #define STATS_PRINT_BUFSIZE 65536 | ||
| 4076 | JEMALLOC_EXPORT void JEMALLOC_NOTHROW | ||
| 4077 | je_malloc_stats_print(void (*write_cb)(void *, const char *), void *cbopaque, | ||
| 4078 | const char *opts) { | ||
| 4079 | tsdn_t *tsdn; | ||
| 4080 | |||
| 4081 | LOG("core.malloc_stats_print.entry", ""); | ||
| 4082 | |||
| 4083 | tsdn = tsdn_fetch(); | ||
| 4084 | check_entry_exit_locking(tsdn); | ||
| 4085 | |||
| 4086 | if (config_debug) { | ||
| 4087 | stats_print(write_cb, cbopaque, opts); | ||
| 4088 | } else { | ||
| 4089 | buf_writer_t buf_writer; | ||
| 4090 | buf_writer_init(tsdn, &buf_writer, write_cb, cbopaque, NULL, | ||
| 4091 | STATS_PRINT_BUFSIZE); | ||
| 4092 | stats_print(buf_writer_cb, &buf_writer, opts); | ||
| 4093 | buf_writer_terminate(tsdn, &buf_writer); | ||
| 4094 | } | ||
| 4095 | |||
| 4096 | check_entry_exit_locking(tsdn); | ||
| 4097 | LOG("core.malloc_stats_print.exit", ""); | ||
| 4098 | } | ||
| 4099 | #undef STATS_PRINT_BUFSIZE | ||
| 4100 | |||
| 4101 | JEMALLOC_ALWAYS_INLINE size_t | ||
| 4102 | je_malloc_usable_size_impl(JEMALLOC_USABLE_SIZE_CONST void *ptr) { | ||
| 4103 | assert(malloc_initialized() || IS_INITIALIZER); | ||
| 4104 | |||
| 4105 | tsdn_t *tsdn = tsdn_fetch(); | ||
| 4106 | check_entry_exit_locking(tsdn); | ||
| 4107 | |||
| 4108 | size_t ret; | ||
| 4109 | if (unlikely(ptr == NULL)) { | ||
| 4110 | ret = 0; | ||
| 4111 | } else { | ||
| 4112 | if (config_debug || force_ivsalloc) { | ||
| 4113 | ret = ivsalloc(tsdn, ptr); | ||
| 4114 | assert(force_ivsalloc || ret != 0); | ||
| 4115 | } else { | ||
| 4116 | ret = isalloc(tsdn, ptr); | ||
| 4117 | } | ||
| 4118 | } | ||
| 4119 | check_entry_exit_locking(tsdn); | ||
| 4120 | |||
| 4121 | return ret; | ||
| 4122 | } | ||
| 4123 | |||
| 4124 | JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW | ||
| 4125 | je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr) { | ||
| 4126 | LOG("core.malloc_usable_size.entry", "ptr: %p", ptr); | ||
| 4127 | |||
| 4128 | size_t ret = je_malloc_usable_size_impl(ptr); | ||
| 4129 | |||
| 4130 | LOG("core.malloc_usable_size.exit", "result: %zu", ret); | ||
| 4131 | return ret; | ||
| 4132 | } | ||
| 4133 | |||
| 4134 | #ifdef JEMALLOC_HAVE_MALLOC_SIZE | ||
| 4135 | JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW | ||
| 4136 | je_malloc_size(const void *ptr) { | ||
| 4137 | LOG("core.malloc_size.entry", "ptr: %p", ptr); | ||
| 4138 | |||
| 4139 | size_t ret = je_malloc_usable_size_impl(ptr); | ||
| 4140 | |||
| 4141 | LOG("core.malloc_size.exit", "result: %zu", ret); | ||
| 4142 | return ret; | ||
| 4143 | } | ||
| 4144 | #endif | ||
| 4145 | |||
| 4146 | static void | ||
| 4147 | batch_alloc_prof_sample_assert(tsd_t *tsd, size_t batch, size_t usize) { | ||
| 4148 | assert(config_prof && opt_prof); | ||
| 4149 | bool prof_sample_event = te_prof_sample_event_lookahead(tsd, | ||
| 4150 | batch * usize); | ||
| 4151 | assert(!prof_sample_event); | ||
| 4152 | size_t surplus; | ||
| 4153 | prof_sample_event = te_prof_sample_event_lookahead_surplus(tsd, | ||
| 4154 | (batch + 1) * usize, &surplus); | ||
| 4155 | assert(prof_sample_event); | ||
| 4156 | assert(surplus < usize); | ||
| 4157 | } | ||
| 4158 | |||
| 4159 | size_t | ||
| 4160 | batch_alloc(void **ptrs, size_t num, size_t size, int flags) { | ||
| 4161 | LOG("core.batch_alloc.entry", | ||
| 4162 | "ptrs: %p, num: %zu, size: %zu, flags: %d", ptrs, num, size, flags); | ||
| 4163 | |||
| 4164 | tsd_t *tsd = tsd_fetch(); | ||
| 4165 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4166 | |||
| 4167 | size_t filled = 0; | ||
| 4168 | |||
| 4169 | if (unlikely(tsd == NULL || tsd_reentrancy_level_get(tsd) > 0)) { | ||
| 4170 | goto label_done; | ||
| 4171 | } | ||
| 4172 | |||
| 4173 | size_t alignment = MALLOCX_ALIGN_GET(flags); | ||
| 4174 | size_t usize; | ||
| 4175 | if (aligned_usize_get(size, alignment, &usize, NULL, false)) { | ||
| 4176 | goto label_done; | ||
| 4177 | } | ||
| 4178 | szind_t ind = sz_size2index(usize); | ||
| 4179 | bool zero = zero_get(MALLOCX_ZERO_GET(flags), /* slow */ true); | ||
| 4180 | |||
| 4181 | /* | ||
| 4182 | * The cache bin and arena will be lazily initialized; it's hard to | ||
| 4183 | * know in advance whether each of them needs to be initialized. | ||
| 4184 | */ | ||
| 4185 | cache_bin_t *bin = NULL; | ||
| 4186 | arena_t *arena = NULL; | ||
| 4187 | |||
| 4188 | size_t nregs = 0; | ||
| 4189 | if (likely(ind < SC_NBINS)) { | ||
| 4190 | nregs = bin_infos[ind].nregs; | ||
| 4191 | assert(nregs > 0); | ||
| 4192 | } | ||
| 4193 | |||
| 4194 | while (filled < num) { | ||
| 4195 | size_t batch = num - filled; | ||
| 4196 | size_t surplus = SIZE_MAX; /* Dead store. */ | ||
| 4197 | bool prof_sample_event = config_prof && opt_prof | ||
| 4198 | && prof_active_get_unlocked() | ||
| 4199 | && te_prof_sample_event_lookahead_surplus(tsd, | ||
| 4200 | batch * usize, &surplus); | ||
| 4201 | |||
| 4202 | if (prof_sample_event) { | ||
| 4203 | /* | ||
| 4204 | * Adjust so that the batch does not trigger prof | ||
| 4205 | * sampling. | ||
| 4206 | */ | ||
| 4207 | batch -= surplus / usize + 1; | ||
| 4208 | batch_alloc_prof_sample_assert(tsd, batch, usize); | ||
| 4209 | } | ||
| 4210 | |||
| 4211 | size_t progress = 0; | ||
| 4212 | |||
| 4213 | if (likely(ind < SC_NBINS) && batch >= nregs) { | ||
| 4214 | if (arena == NULL) { | ||
| 4215 | unsigned arena_ind = mallocx_arena_get(flags); | ||
| 4216 | if (arena_get_from_ind(tsd, arena_ind, | ||
| 4217 | &arena)) { | ||
| 4218 | goto label_done; | ||
| 4219 | } | ||
| 4220 | if (arena == NULL) { | ||
| 4221 | arena = arena_choose(tsd, NULL); | ||
| 4222 | } | ||
| 4223 | if (unlikely(arena == NULL)) { | ||
| 4224 | goto label_done; | ||
| 4225 | } | ||
| 4226 | } | ||
| 4227 | size_t arena_batch = batch - batch % nregs; | ||
| 4228 | size_t n = arena_fill_small_fresh(tsd_tsdn(tsd), arena, | ||
| 4229 | ind, ptrs + filled, arena_batch, zero); | ||
| 4230 | progress += n; | ||
| 4231 | filled += n; | ||
| 4232 | } | ||
| 4233 | |||
| 4234 | if (likely(ind < nhbins) && progress < batch) { | ||
| 4235 | if (bin == NULL) { | ||
| 4236 | unsigned tcache_ind = mallocx_tcache_get(flags); | ||
| 4237 | tcache_t *tcache = tcache_get_from_ind(tsd, | ||
| 4238 | tcache_ind, /* slow */ true, | ||
| 4239 | /* is_alloc */ true); | ||
| 4240 | if (tcache != NULL) { | ||
| 4241 | bin = &tcache->bins[ind]; | ||
| 4242 | } | ||
| 4243 | } | ||
| 4244 | /* | ||
| 4245 | * If we don't have a tcache bin, we don't want to | ||
| 4246 | * immediately give up, because there's the possibility | ||
| 4247 | * that the user explicitly requested to bypass the | ||
| 4248 | * tcache, or that the user explicitly turned off the | ||
| 4249 | * tcache; in such cases, we go through the slow path, | ||
| 4250 | * i.e. the mallocx() call at the end of the while loop. | ||
| 4251 | */ | ||
| 4252 | if (bin != NULL) { | ||
| 4253 | size_t bin_batch = batch - progress; | ||
| 4254 | /* | ||
| 4255 | * n can be less than bin_batch, meaning that | ||
| 4256 | * the cache bin does not have enough memory. | ||
| 4257 | * In such cases, we rely on the slow path, | ||
| 4258 | * i.e. the mallocx() call at the end of the | ||
| 4259 | * while loop, to fill in the cache, and in the | ||
| 4260 | * next iteration of the while loop, the tcache | ||
| 4261 | * will contain a lot of memory, and we can | ||
| 4262 | * harvest them here. Compared to the | ||
| 4263 | * alternative approach where we directly go to | ||
| 4264 | * the arena bins here, the overhead of our | ||
| 4265 | * current approach should usually be minimal, | ||
| 4266 | * since we never try to fetch more memory than | ||
| 4267 | * what a slab contains via the tcache. An | ||
| 4268 | * additional benefit is that the tcache will | ||
| 4269 | * not be empty for the next allocation request. | ||
| 4270 | */ | ||
| 4271 | size_t n = cache_bin_alloc_batch(bin, bin_batch, | ||
| 4272 | ptrs + filled); | ||
| 4273 | if (config_stats) { | ||
| 4274 | bin->tstats.nrequests += n; | ||
| 4275 | } | ||
| 4276 | if (zero) { | ||
| 4277 | for (size_t i = 0; i < n; ++i) { | ||
| 4278 | memset(ptrs[filled + i], 0, | ||
| 4279 | usize); | ||
| 4280 | } | ||
| 4281 | } | ||
| 4282 | if (config_prof && opt_prof | ||
| 4283 | && unlikely(ind >= SC_NBINS)) { | ||
| 4284 | for (size_t i = 0; i < n; ++i) { | ||
| 4285 | prof_tctx_reset_sampled(tsd, | ||
| 4286 | ptrs[filled + i]); | ||
| 4287 | } | ||
| 4288 | } | ||
| 4289 | progress += n; | ||
| 4290 | filled += n; | ||
| 4291 | } | ||
| 4292 | } | ||
| 4293 | |||
| 4294 | /* | ||
| 4295 | * For thread events other than prof sampling, trigger them as | ||
| 4296 | * if there's a single allocation of size (n * usize). This is | ||
| 4297 | * fine because: | ||
| 4298 | * (a) these events do not alter the allocation itself, and | ||
| 4299 | * (b) it's possible that some event would have been triggered | ||
| 4300 | * multiple times, instead of only once, if the allocations | ||
| 4301 | * were handled individually, but it would do no harm (or | ||
| 4302 | * even be beneficial) to coalesce the triggerings. | ||
| 4303 | */ | ||
| 4304 | thread_alloc_event(tsd, progress * usize); | ||
| 4305 | |||
| 4306 | if (progress < batch || prof_sample_event) { | ||
| 4307 | void *p = je_mallocx(size, flags); | ||
| 4308 | if (p == NULL) { /* OOM */ | ||
| 4309 | break; | ||
| 4310 | } | ||
| 4311 | if (progress == batch) { | ||
| 4312 | assert(prof_sampled(tsd, p)); | ||
| 4313 | } | ||
| 4314 | ptrs[filled++] = p; | ||
| 4315 | } | ||
| 4316 | } | ||
| 4317 | |||
| 4318 | label_done: | ||
| 4319 | check_entry_exit_locking(tsd_tsdn(tsd)); | ||
| 4320 | LOG("core.batch_alloc.exit", "result: %zu", filled); | ||
| 4321 | return filled; | ||
| 4322 | } | ||
| 4323 | |||
| 4324 | /* | ||
| 4325 | * End non-standard functions. | ||
| 4326 | */ | ||
| 4327 | /******************************************************************************/ | ||
| 4328 | /* | ||
| 4329 | * The following functions are used by threading libraries for protection of | ||
| 4330 | * malloc during fork(). | ||
| 4331 | */ | ||
| 4332 | |||
| 4333 | /* | ||
| 4334 | * If an application creates a thread before doing any allocation in the main | ||
| 4335 | * thread, then calls fork(2) in the main thread followed by memory allocation | ||
| 4336 | * in the child process, a race can occur that results in deadlock within the | ||
| 4337 | * child: the main thread may have forked while the created thread had | ||
| 4338 | * partially initialized the allocator. Ordinarily jemalloc prevents | ||
| 4339 | * fork/malloc races via the following functions it registers during | ||
| 4340 | * initialization using pthread_atfork(), but of course that does no good if | ||
| 4341 | * the allocator isn't fully initialized at fork time. The following library | ||
| 4342 | * constructor is a partial solution to this problem. It may still be possible | ||
| 4343 | * to trigger the deadlock described above, but doing so would involve forking | ||
| 4344 | * via a library constructor that runs before jemalloc's runs. | ||
| 4345 | */ | ||
| 4346 | #ifndef JEMALLOC_JET | ||
| 4347 | JEMALLOC_ATTR(constructor) | ||
| 4348 | static void | ||
| 4349 | jemalloc_constructor(void) { | ||
| 4350 | malloc_init(); | ||
| 4351 | } | ||
| 4352 | #endif | ||
| 4353 | |||
| 4354 | #ifndef JEMALLOC_MUTEX_INIT_CB | ||
| 4355 | void | ||
| 4356 | jemalloc_prefork(void) | ||
| 4357 | #else | ||
| 4358 | JEMALLOC_EXPORT void | ||
| 4359 | _malloc_prefork(void) | ||
| 4360 | #endif | ||
| 4361 | { | ||
| 4362 | tsd_t *tsd; | ||
| 4363 | unsigned i, j, narenas; | ||
| 4364 | arena_t *arena; | ||
| 4365 | |||
| 4366 | #ifdef JEMALLOC_MUTEX_INIT_CB | ||
| 4367 | if (!malloc_initialized()) { | ||
| 4368 | return; | ||
| 4369 | } | ||
| 4370 | #endif | ||
| 4371 | assert(malloc_initialized()); | ||
| 4372 | |||
| 4373 | tsd = tsd_fetch(); | ||
| 4374 | |||
| 4375 | narenas = narenas_total_get(); | ||
| 4376 | |||
| 4377 | witness_prefork(tsd_witness_tsdp_get(tsd)); | ||
| 4378 | /* Acquire all mutexes in a safe order. */ | ||
| 4379 | ctl_prefork(tsd_tsdn(tsd)); | ||
| 4380 | tcache_prefork(tsd_tsdn(tsd)); | ||
| 4381 | malloc_mutex_prefork(tsd_tsdn(tsd), &arenas_lock); | ||
| 4382 | if (have_background_thread) { | ||
| 4383 | background_thread_prefork0(tsd_tsdn(tsd)); | ||
| 4384 | } | ||
| 4385 | prof_prefork0(tsd_tsdn(tsd)); | ||
| 4386 | if (have_background_thread) { | ||
| 4387 | background_thread_prefork1(tsd_tsdn(tsd)); | ||
| 4388 | } | ||
| 4389 | /* Break arena prefork into stages to preserve lock order. */ | ||
| 4390 | for (i = 0; i < 9; i++) { | ||
| 4391 | for (j = 0; j < narenas; j++) { | ||
| 4392 | if ((arena = arena_get(tsd_tsdn(tsd), j, false)) != | ||
| 4393 | NULL) { | ||
| 4394 | switch (i) { | ||
| 4395 | case 0: | ||
| 4396 | arena_prefork0(tsd_tsdn(tsd), arena); | ||
| 4397 | break; | ||
| 4398 | case 1: | ||
| 4399 | arena_prefork1(tsd_tsdn(tsd), arena); | ||
| 4400 | break; | ||
| 4401 | case 2: | ||
| 4402 | arena_prefork2(tsd_tsdn(tsd), arena); | ||
| 4403 | break; | ||
| 4404 | case 3: | ||
| 4405 | arena_prefork3(tsd_tsdn(tsd), arena); | ||
| 4406 | break; | ||
| 4407 | case 4: | ||
| 4408 | arena_prefork4(tsd_tsdn(tsd), arena); | ||
| 4409 | break; | ||
| 4410 | case 5: | ||
| 4411 | arena_prefork5(tsd_tsdn(tsd), arena); | ||
| 4412 | break; | ||
| 4413 | case 6: | ||
| 4414 | arena_prefork6(tsd_tsdn(tsd), arena); | ||
| 4415 | break; | ||
| 4416 | case 7: | ||
| 4417 | arena_prefork7(tsd_tsdn(tsd), arena); | ||
| 4418 | break; | ||
| 4419 | case 8: | ||
| 4420 | arena_prefork8(tsd_tsdn(tsd), arena); | ||
| 4421 | break; | ||
| 4422 | default: not_reached(); | ||
| 4423 | } | ||
| 4424 | } | ||
| 4425 | } | ||
| 4426 | |||
| 4427 | } | ||
| 4428 | prof_prefork1(tsd_tsdn(tsd)); | ||
| 4429 | stats_prefork(tsd_tsdn(tsd)); | ||
| 4430 | tsd_prefork(tsd); | ||
| 4431 | } | ||
| 4432 | |||
| 4433 | #ifndef JEMALLOC_MUTEX_INIT_CB | ||
| 4434 | void | ||
| 4435 | jemalloc_postfork_parent(void) | ||
| 4436 | #else | ||
| 4437 | JEMALLOC_EXPORT void | ||
| 4438 | _malloc_postfork(void) | ||
| 4439 | #endif | ||
| 4440 | { | ||
| 4441 | tsd_t *tsd; | ||
| 4442 | unsigned i, narenas; | ||
| 4443 | |||
| 4444 | #ifdef JEMALLOC_MUTEX_INIT_CB | ||
| 4445 | if (!malloc_initialized()) { | ||
| 4446 | return; | ||
| 4447 | } | ||
| 4448 | #endif | ||
| 4449 | assert(malloc_initialized()); | ||
| 4450 | |||
| 4451 | tsd = tsd_fetch(); | ||
| 4452 | |||
| 4453 | tsd_postfork_parent(tsd); | ||
| 4454 | |||
| 4455 | witness_postfork_parent(tsd_witness_tsdp_get(tsd)); | ||
| 4456 | /* Release all mutexes, now that fork() has completed. */ | ||
| 4457 | stats_postfork_parent(tsd_tsdn(tsd)); | ||
| 4458 | for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { | ||
| 4459 | arena_t *arena; | ||
| 4460 | |||
| 4461 | if ((arena = arena_get(tsd_tsdn(tsd), i, false)) != NULL) { | ||
| 4462 | arena_postfork_parent(tsd_tsdn(tsd), arena); | ||
| 4463 | } | ||
| 4464 | } | ||
| 4465 | prof_postfork_parent(tsd_tsdn(tsd)); | ||
| 4466 | if (have_background_thread) { | ||
| 4467 | background_thread_postfork_parent(tsd_tsdn(tsd)); | ||
| 4468 | } | ||
| 4469 | malloc_mutex_postfork_parent(tsd_tsdn(tsd), &arenas_lock); | ||
| 4470 | tcache_postfork_parent(tsd_tsdn(tsd)); | ||
| 4471 | ctl_postfork_parent(tsd_tsdn(tsd)); | ||
| 4472 | } | ||
| 4473 | |||
| 4474 | void | ||
| 4475 | jemalloc_postfork_child(void) { | ||
| 4476 | tsd_t *tsd; | ||
| 4477 | unsigned i, narenas; | ||
| 4478 | |||
| 4479 | assert(malloc_initialized()); | ||
| 4480 | |||
| 4481 | tsd = tsd_fetch(); | ||
| 4482 | |||
| 4483 | tsd_postfork_child(tsd); | ||
| 4484 | |||
| 4485 | witness_postfork_child(tsd_witness_tsdp_get(tsd)); | ||
| 4486 | /* Release all mutexes, now that fork() has completed. */ | ||
| 4487 | stats_postfork_child(tsd_tsdn(tsd)); | ||
| 4488 | for (i = 0, narenas = narenas_total_get(); i < narenas; i++) { | ||
| 4489 | arena_t *arena; | ||
| 4490 | |||
| 4491 | if ((arena = arena_get(tsd_tsdn(tsd), i, false)) != NULL) { | ||
| 4492 | arena_postfork_child(tsd_tsdn(tsd), arena); | ||
| 4493 | } | ||
| 4494 | } | ||
| 4495 | prof_postfork_child(tsd_tsdn(tsd)); | ||
| 4496 | if (have_background_thread) { | ||
| 4497 | background_thread_postfork_child(tsd_tsdn(tsd)); | ||
| 4498 | } | ||
| 4499 | malloc_mutex_postfork_child(tsd_tsdn(tsd), &arenas_lock); | ||
| 4500 | tcache_postfork_child(tsd_tsdn(tsd)); | ||
| 4501 | ctl_postfork_child(tsd_tsdn(tsd)); | ||
| 4502 | } | ||
| 4503 | |||
| 4504 | /******************************************************************************/ | ||
| 4505 | |||
| 4506 | /* Helps the application decide if a pointer is worth re-allocating in order to reduce fragmentation. | ||
| 4507 | * returns 1 if the allocation should be moved, and 0 if the allocation be kept. | ||
| 4508 | * If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */ | ||
| 4509 | JEMALLOC_EXPORT int JEMALLOC_NOTHROW | ||
| 4510 | get_defrag_hint(void* ptr) { | ||
| 4511 | assert(ptr != NULL); | ||
| 4512 | return iget_defrag_hint(TSDN_NULL, ptr); | ||
| 4513 | } | ||
| 4514 | |||
| 4515 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 4516 | void JEMALLOC_NOTHROW * | ||
| 4517 | JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1) | ||
| 4518 | malloc_with_usize(size_t size, size_t *usize) { | ||
| 4519 | return je_malloc_internal(size, usize); | ||
| 4520 | } | ||
| 4521 | |||
| 4522 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 4523 | void JEMALLOC_NOTHROW * | ||
| 4524 | JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE2(1, 2) | ||
| 4525 | calloc_with_usize(size_t num, size_t size, size_t *usize) { | ||
| 4526 | return je_calloc_internal(num, size, usize); | ||
| 4527 | } | ||
| 4528 | |||
| 4529 | JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN | ||
| 4530 | void JEMALLOC_NOTHROW * | ||
| 4531 | JEMALLOC_ALLOC_SIZE(2) | ||
| 4532 | realloc_with_usize(void *ptr, size_t size, size_t *old_usize, size_t *new_usize) { | ||
| 4533 | return je_realloc_internal(ptr, size, old_usize, new_usize); | ||
| 4534 | } | ||
| 4535 | |||
| 4536 | JEMALLOC_EXPORT void JEMALLOC_NOTHROW | ||
| 4537 | free_with_usize(void *ptr, size_t *usize) { | ||
| 4538 | je_free_internal(ptr, usize); | ||
| 4539 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/jemalloc_cpp.cpp b/examples/redis-unstable/deps/jemalloc/src/jemalloc_cpp.cpp deleted file mode 100644 index aeff8c6..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/jemalloc_cpp.cpp +++ /dev/null | |||
| @@ -1,254 +0,0 @@ | |||
| 1 | #include <mutex> | ||
| 2 | #include <new> | ||
| 3 | |||
| 4 | #define JEMALLOC_CPP_CPP_ | ||
| 5 | #ifdef __cplusplus | ||
| 6 | extern "C" { | ||
| 7 | #endif | ||
| 8 | |||
| 9 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 10 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 11 | |||
| 12 | #ifdef __cplusplus | ||
| 13 | } | ||
| 14 | #endif | ||
| 15 | |||
| 16 | // All operators in this file are exported. | ||
| 17 | |||
| 18 | // Possibly alias hidden versions of malloc and sdallocx to avoid an extra plt | ||
| 19 | // thunk? | ||
| 20 | // | ||
| 21 | // extern __typeof (sdallocx) sdallocx_int | ||
| 22 | // __attribute ((alias ("sdallocx"), | ||
| 23 | // visibility ("hidden"))); | ||
| 24 | // | ||
| 25 | // ... but it needs to work with jemalloc namespaces. | ||
| 26 | |||
| 27 | void *operator new(std::size_t size); | ||
| 28 | void *operator new[](std::size_t size); | ||
| 29 | void *operator new(std::size_t size, const std::nothrow_t &) noexcept; | ||
| 30 | void *operator new[](std::size_t size, const std::nothrow_t &) noexcept; | ||
| 31 | void operator delete(void *ptr) noexcept; | ||
| 32 | void operator delete[](void *ptr) noexcept; | ||
| 33 | void operator delete(void *ptr, const std::nothrow_t &) noexcept; | ||
| 34 | void operator delete[](void *ptr, const std::nothrow_t &) noexcept; | ||
| 35 | |||
| 36 | #if __cpp_sized_deallocation >= 201309 | ||
| 37 | /* C++14's sized-delete operators. */ | ||
| 38 | void operator delete(void *ptr, std::size_t size) noexcept; | ||
| 39 | void operator delete[](void *ptr, std::size_t size) noexcept; | ||
| 40 | #endif | ||
| 41 | |||
| 42 | #if __cpp_aligned_new >= 201606 | ||
| 43 | /* C++17's over-aligned operators. */ | ||
| 44 | void *operator new(std::size_t size, std::align_val_t); | ||
| 45 | void *operator new(std::size_t size, std::align_val_t, const std::nothrow_t &) noexcept; | ||
| 46 | void *operator new[](std::size_t size, std::align_val_t); | ||
| 47 | void *operator new[](std::size_t size, std::align_val_t, const std::nothrow_t &) noexcept; | ||
| 48 | void operator delete(void* ptr, std::align_val_t) noexcept; | ||
| 49 | void operator delete(void* ptr, std::align_val_t, const std::nothrow_t &) noexcept; | ||
| 50 | void operator delete(void* ptr, std::size_t size, std::align_val_t al) noexcept; | ||
| 51 | void operator delete[](void* ptr, std::align_val_t) noexcept; | ||
| 52 | void operator delete[](void* ptr, std::align_val_t, const std::nothrow_t &) noexcept; | ||
| 53 | void operator delete[](void* ptr, std::size_t size, std::align_val_t al) noexcept; | ||
| 54 | #endif | ||
| 55 | |||
| 56 | JEMALLOC_NOINLINE | ||
| 57 | static void * | ||
| 58 | handleOOM(std::size_t size, bool nothrow) { | ||
| 59 | if (opt_experimental_infallible_new) { | ||
| 60 | safety_check_fail("<jemalloc>: Allocation failed and " | ||
| 61 | "opt.experimental_infallible_new is true. Aborting.\n"); | ||
| 62 | return nullptr; | ||
| 63 | } | ||
| 64 | |||
| 65 | void *ptr = nullptr; | ||
| 66 | |||
| 67 | while (ptr == nullptr) { | ||
| 68 | std::new_handler handler; | ||
| 69 | // GCC-4.8 and clang 4.0 do not have std::get_new_handler. | ||
| 70 | { | ||
| 71 | static std::mutex mtx; | ||
| 72 | std::lock_guard<std::mutex> lock(mtx); | ||
| 73 | |||
| 74 | handler = std::set_new_handler(nullptr); | ||
| 75 | std::set_new_handler(handler); | ||
| 76 | } | ||
| 77 | if (handler == nullptr) | ||
| 78 | break; | ||
| 79 | |||
| 80 | try { | ||
| 81 | handler(); | ||
| 82 | } catch (const std::bad_alloc &) { | ||
| 83 | break; | ||
| 84 | } | ||
| 85 | |||
| 86 | ptr = je_malloc(size); | ||
| 87 | } | ||
| 88 | |||
| 89 | if (ptr == nullptr && !nothrow) | ||
| 90 | std::__throw_bad_alloc(); | ||
| 91 | return ptr; | ||
| 92 | } | ||
| 93 | |||
| 94 | template <bool IsNoExcept> | ||
| 95 | JEMALLOC_NOINLINE | ||
| 96 | static void * | ||
| 97 | fallback_impl(std::size_t size, std::size_t *usize) noexcept(IsNoExcept) { | ||
| 98 | void *ptr = malloc_default(size, NULL); | ||
| 99 | if (likely(ptr != nullptr)) { | ||
| 100 | return ptr; | ||
| 101 | } | ||
| 102 | return handleOOM(size, IsNoExcept); | ||
| 103 | } | ||
| 104 | |||
| 105 | template <bool IsNoExcept> | ||
| 106 | JEMALLOC_ALWAYS_INLINE | ||
| 107 | void * | ||
| 108 | newImpl(std::size_t size) noexcept(IsNoExcept) { | ||
| 109 | return imalloc_fastpath(size, &fallback_impl<IsNoExcept>, NULL); | ||
| 110 | } | ||
| 111 | |||
| 112 | void * | ||
| 113 | operator new(std::size_t size) { | ||
| 114 | return newImpl<false>(size); | ||
| 115 | } | ||
| 116 | |||
| 117 | void * | ||
| 118 | operator new[](std::size_t size) { | ||
| 119 | return newImpl<false>(size); | ||
| 120 | } | ||
| 121 | |||
| 122 | void * | ||
| 123 | operator new(std::size_t size, const std::nothrow_t &) noexcept { | ||
| 124 | return newImpl<true>(size); | ||
| 125 | } | ||
| 126 | |||
| 127 | void * | ||
| 128 | operator new[](std::size_t size, const std::nothrow_t &) noexcept { | ||
| 129 | return newImpl<true>(size); | ||
| 130 | } | ||
| 131 | |||
| 132 | #if __cpp_aligned_new >= 201606 | ||
| 133 | |||
| 134 | template <bool IsNoExcept> | ||
| 135 | JEMALLOC_ALWAYS_INLINE | ||
| 136 | void * | ||
| 137 | alignedNewImpl(std::size_t size, std::align_val_t alignment) noexcept(IsNoExcept) { | ||
| 138 | void *ptr = je_aligned_alloc(static_cast<std::size_t>(alignment), size); | ||
| 139 | if (likely(ptr != nullptr)) { | ||
| 140 | return ptr; | ||
| 141 | } | ||
| 142 | |||
| 143 | return handleOOM(size, IsNoExcept); | ||
| 144 | } | ||
| 145 | |||
| 146 | void * | ||
| 147 | operator new(std::size_t size, std::align_val_t alignment) { | ||
| 148 | return alignedNewImpl<false>(size, alignment); | ||
| 149 | } | ||
| 150 | |||
| 151 | void * | ||
| 152 | operator new[](std::size_t size, std::align_val_t alignment) { | ||
| 153 | return alignedNewImpl<false>(size, alignment); | ||
| 154 | } | ||
| 155 | |||
| 156 | void * | ||
| 157 | operator new(std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept { | ||
| 158 | return alignedNewImpl<true>(size, alignment); | ||
| 159 | } | ||
| 160 | |||
| 161 | void * | ||
| 162 | operator new[](std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept { | ||
| 163 | return alignedNewImpl<true>(size, alignment); | ||
| 164 | } | ||
| 165 | |||
| 166 | #endif // __cpp_aligned_new | ||
| 167 | |||
| 168 | void | ||
| 169 | operator delete(void *ptr) noexcept { | ||
| 170 | je_free(ptr); | ||
| 171 | } | ||
| 172 | |||
| 173 | void | ||
| 174 | operator delete[](void *ptr) noexcept { | ||
| 175 | je_free(ptr); | ||
| 176 | } | ||
| 177 | |||
| 178 | void | ||
| 179 | operator delete(void *ptr, const std::nothrow_t &) noexcept { | ||
| 180 | je_free(ptr); | ||
| 181 | } | ||
| 182 | |||
| 183 | void operator delete[](void *ptr, const std::nothrow_t &) noexcept { | ||
| 184 | je_free(ptr); | ||
| 185 | } | ||
| 186 | |||
| 187 | #if __cpp_sized_deallocation >= 201309 | ||
| 188 | |||
| 189 | JEMALLOC_ALWAYS_INLINE | ||
| 190 | void | ||
| 191 | sizedDeleteImpl(void* ptr, std::size_t size) noexcept { | ||
| 192 | if (unlikely(ptr == nullptr)) { | ||
| 193 | return; | ||
| 194 | } | ||
| 195 | je_sdallocx_noflags(ptr, size); | ||
| 196 | } | ||
| 197 | |||
| 198 | void | ||
| 199 | operator delete(void *ptr, std::size_t size) noexcept { | ||
| 200 | sizedDeleteImpl(ptr, size); | ||
| 201 | } | ||
| 202 | |||
| 203 | void | ||
| 204 | operator delete[](void *ptr, std::size_t size) noexcept { | ||
| 205 | sizedDeleteImpl(ptr, size); | ||
| 206 | } | ||
| 207 | |||
| 208 | #endif // __cpp_sized_deallocation | ||
| 209 | |||
| 210 | #if __cpp_aligned_new >= 201606 | ||
| 211 | |||
| 212 | JEMALLOC_ALWAYS_INLINE | ||
| 213 | void | ||
| 214 | alignedSizedDeleteImpl(void* ptr, std::size_t size, std::align_val_t alignment) noexcept { | ||
| 215 | if (config_debug) { | ||
| 216 | assert(((size_t)alignment & ((size_t)alignment - 1)) == 0); | ||
| 217 | } | ||
| 218 | if (unlikely(ptr == nullptr)) { | ||
| 219 | return; | ||
| 220 | } | ||
| 221 | je_sdallocx(ptr, size, MALLOCX_ALIGN(alignment)); | ||
| 222 | } | ||
| 223 | |||
| 224 | void | ||
| 225 | operator delete(void* ptr, std::align_val_t) noexcept { | ||
| 226 | je_free(ptr); | ||
| 227 | } | ||
| 228 | |||
| 229 | void | ||
| 230 | operator delete[](void* ptr, std::align_val_t) noexcept { | ||
| 231 | je_free(ptr); | ||
| 232 | } | ||
| 233 | |||
| 234 | void | ||
| 235 | operator delete(void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { | ||
| 236 | je_free(ptr); | ||
| 237 | } | ||
| 238 | |||
| 239 | void | ||
| 240 | operator delete[](void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { | ||
| 241 | je_free(ptr); | ||
| 242 | } | ||
| 243 | |||
| 244 | void | ||
| 245 | operator delete(void* ptr, std::size_t size, std::align_val_t alignment) noexcept { | ||
| 246 | alignedSizedDeleteImpl(ptr, size, alignment); | ||
| 247 | } | ||
| 248 | |||
| 249 | void | ||
| 250 | operator delete[](void* ptr, std::size_t size, std::align_val_t alignment) noexcept { | ||
| 251 | alignedSizedDeleteImpl(ptr, size, alignment); | ||
| 252 | } | ||
| 253 | |||
| 254 | #endif // __cpp_aligned_new | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/large.c b/examples/redis-unstable/deps/jemalloc/src/large.c deleted file mode 100644 index 5fc4bf5..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/large.c +++ /dev/null | |||
| @@ -1,322 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/emap.h" | ||
| 6 | #include "jemalloc/internal/extent_mmap.h" | ||
| 7 | #include "jemalloc/internal/mutex.h" | ||
| 8 | #include "jemalloc/internal/prof_recent.h" | ||
| 9 | #include "jemalloc/internal/util.h" | ||
| 10 | |||
| 11 | /******************************************************************************/ | ||
| 12 | |||
| 13 | void * | ||
| 14 | large_malloc(tsdn_t *tsdn, arena_t *arena, size_t usize, bool zero) { | ||
| 15 | assert(usize == sz_s2u(usize)); | ||
| 16 | |||
| 17 | return large_palloc(tsdn, arena, usize, CACHELINE, zero); | ||
| 18 | } | ||
| 19 | |||
| 20 | void * | ||
| 21 | large_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment, | ||
| 22 | bool zero) { | ||
| 23 | size_t ausize; | ||
| 24 | edata_t *edata; | ||
| 25 | UNUSED bool idump JEMALLOC_CC_SILENCE_INIT(false); | ||
| 26 | |||
| 27 | assert(!tsdn_null(tsdn) || arena != NULL); | ||
| 28 | |||
| 29 | ausize = sz_sa2u(usize, alignment); | ||
| 30 | if (unlikely(ausize == 0 || ausize > SC_LARGE_MAXCLASS)) { | ||
| 31 | return NULL; | ||
| 32 | } | ||
| 33 | |||
| 34 | if (likely(!tsdn_null(tsdn))) { | ||
| 35 | arena = arena_choose_maybe_huge(tsdn_tsd(tsdn), arena, usize); | ||
| 36 | } | ||
| 37 | if (unlikely(arena == NULL) || (edata = arena_extent_alloc_large(tsdn, | ||
| 38 | arena, usize, alignment, zero)) == NULL) { | ||
| 39 | return NULL; | ||
| 40 | } | ||
| 41 | |||
| 42 | /* See comments in arena_bin_slabs_full_insert(). */ | ||
| 43 | if (!arena_is_auto(arena)) { | ||
| 44 | /* Insert edata into large. */ | ||
| 45 | malloc_mutex_lock(tsdn, &arena->large_mtx); | ||
| 46 | edata_list_active_append(&arena->large, edata); | ||
| 47 | malloc_mutex_unlock(tsdn, &arena->large_mtx); | ||
| 48 | } | ||
| 49 | |||
| 50 | arena_decay_tick(tsdn, arena); | ||
| 51 | return edata_addr_get(edata); | ||
| 52 | } | ||
| 53 | |||
| 54 | static bool | ||
| 55 | large_ralloc_no_move_shrink(tsdn_t *tsdn, edata_t *edata, size_t usize) { | ||
| 56 | arena_t *arena = arena_get_from_edata(edata); | ||
| 57 | ehooks_t *ehooks = arena_get_ehooks(arena); | ||
| 58 | size_t old_size = edata_size_get(edata); | ||
| 59 | size_t old_usize = edata_usize_get(edata); | ||
| 60 | |||
| 61 | assert(old_usize > usize); | ||
| 62 | |||
| 63 | if (ehooks_split_will_fail(ehooks)) { | ||
| 64 | return true; | ||
| 65 | } | ||
| 66 | |||
| 67 | bool deferred_work_generated = false; | ||
| 68 | bool err = pa_shrink(tsdn, &arena->pa_shard, edata, old_size, | ||
| 69 | usize + sz_large_pad, sz_size2index(usize), | ||
| 70 | &deferred_work_generated); | ||
| 71 | if (err) { | ||
| 72 | return true; | ||
| 73 | } | ||
| 74 | if (deferred_work_generated) { | ||
| 75 | arena_handle_deferred_work(tsdn, arena); | ||
| 76 | } | ||
| 77 | arena_extent_ralloc_large_shrink(tsdn, arena, edata, old_usize); | ||
| 78 | |||
| 79 | return false; | ||
| 80 | } | ||
| 81 | |||
| 82 | static bool | ||
| 83 | large_ralloc_no_move_expand(tsdn_t *tsdn, edata_t *edata, size_t usize, | ||
| 84 | bool zero) { | ||
| 85 | arena_t *arena = arena_get_from_edata(edata); | ||
| 86 | |||
| 87 | size_t old_size = edata_size_get(edata); | ||
| 88 | size_t old_usize = edata_usize_get(edata); | ||
| 89 | size_t new_size = usize + sz_large_pad; | ||
| 90 | |||
| 91 | szind_t szind = sz_size2index(usize); | ||
| 92 | |||
| 93 | bool deferred_work_generated = false; | ||
| 94 | bool err = pa_expand(tsdn, &arena->pa_shard, edata, old_size, new_size, | ||
| 95 | szind, zero, &deferred_work_generated); | ||
| 96 | |||
| 97 | if (deferred_work_generated) { | ||
| 98 | arena_handle_deferred_work(tsdn, arena); | ||
| 99 | } | ||
| 100 | |||
| 101 | if (err) { | ||
| 102 | return true; | ||
| 103 | } | ||
| 104 | |||
| 105 | if (zero) { | ||
| 106 | if (opt_cache_oblivious) { | ||
| 107 | assert(sz_large_pad == PAGE); | ||
| 108 | /* | ||
| 109 | * Zero the trailing bytes of the original allocation's | ||
| 110 | * last page, since they are in an indeterminate state. | ||
| 111 | * There will always be trailing bytes, because ptr's | ||
| 112 | * offset from the beginning of the extent is a multiple | ||
| 113 | * of CACHELINE in [0 .. PAGE). | ||
| 114 | */ | ||
| 115 | void *zbase = (void *) | ||
| 116 | ((uintptr_t)edata_addr_get(edata) + old_usize); | ||
| 117 | void *zpast = PAGE_ADDR2BASE((void *)((uintptr_t)zbase + | ||
| 118 | PAGE)); | ||
| 119 | size_t nzero = (uintptr_t)zpast - (uintptr_t)zbase; | ||
| 120 | assert(nzero > 0); | ||
| 121 | memset(zbase, 0, nzero); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | arena_extent_ralloc_large_expand(tsdn, arena, edata, old_usize); | ||
| 125 | |||
| 126 | return false; | ||
| 127 | } | ||
| 128 | |||
| 129 | bool | ||
| 130 | large_ralloc_no_move(tsdn_t *tsdn, edata_t *edata, size_t usize_min, | ||
| 131 | size_t usize_max, bool zero) { | ||
| 132 | size_t oldusize = edata_usize_get(edata); | ||
| 133 | |||
| 134 | /* The following should have been caught by callers. */ | ||
| 135 | assert(usize_min > 0 && usize_max <= SC_LARGE_MAXCLASS); | ||
| 136 | /* Both allocation sizes must be large to avoid a move. */ | ||
| 137 | assert(oldusize >= SC_LARGE_MINCLASS | ||
| 138 | && usize_max >= SC_LARGE_MINCLASS); | ||
| 139 | |||
| 140 | if (usize_max > oldusize) { | ||
| 141 | /* Attempt to expand the allocation in-place. */ | ||
| 142 | if (!large_ralloc_no_move_expand(tsdn, edata, usize_max, | ||
| 143 | zero)) { | ||
| 144 | arena_decay_tick(tsdn, arena_get_from_edata(edata)); | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | /* Try again, this time with usize_min. */ | ||
| 148 | if (usize_min < usize_max && usize_min > oldusize && | ||
| 149 | large_ralloc_no_move_expand(tsdn, edata, usize_min, zero)) { | ||
| 150 | arena_decay_tick(tsdn, arena_get_from_edata(edata)); | ||
| 151 | return false; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | /* | ||
| 156 | * Avoid moving the allocation if the existing extent size accommodates | ||
| 157 | * the new size. | ||
| 158 | */ | ||
| 159 | if (oldusize >= usize_min && oldusize <= usize_max) { | ||
| 160 | arena_decay_tick(tsdn, arena_get_from_edata(edata)); | ||
| 161 | return false; | ||
| 162 | } | ||
| 163 | |||
| 164 | /* Attempt to shrink the allocation in-place. */ | ||
| 165 | if (oldusize > usize_max) { | ||
| 166 | if (!large_ralloc_no_move_shrink(tsdn, edata, usize_max)) { | ||
| 167 | arena_decay_tick(tsdn, arena_get_from_edata(edata)); | ||
| 168 | return false; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | return true; | ||
| 172 | } | ||
| 173 | |||
| 174 | static void * | ||
| 175 | large_ralloc_move_helper(tsdn_t *tsdn, arena_t *arena, size_t usize, | ||
| 176 | size_t alignment, bool zero) { | ||
| 177 | if (alignment <= CACHELINE) { | ||
| 178 | return large_malloc(tsdn, arena, usize, zero); | ||
| 179 | } | ||
| 180 | return large_palloc(tsdn, arena, usize, alignment, zero); | ||
| 181 | } | ||
| 182 | |||
| 183 | void * | ||
| 184 | large_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t usize, | ||
| 185 | size_t alignment, bool zero, tcache_t *tcache, | ||
| 186 | hook_ralloc_args_t *hook_args) { | ||
| 187 | edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr); | ||
| 188 | |||
| 189 | size_t oldusize = edata_usize_get(edata); | ||
| 190 | /* The following should have been caught by callers. */ | ||
| 191 | assert(usize > 0 && usize <= SC_LARGE_MAXCLASS); | ||
| 192 | /* Both allocation sizes must be large to avoid a move. */ | ||
| 193 | assert(oldusize >= SC_LARGE_MINCLASS | ||
| 194 | && usize >= SC_LARGE_MINCLASS); | ||
| 195 | |||
| 196 | /* Try to avoid moving the allocation. */ | ||
| 197 | if (!large_ralloc_no_move(tsdn, edata, usize, usize, zero)) { | ||
| 198 | hook_invoke_expand(hook_args->is_realloc | ||
| 199 | ? hook_expand_realloc : hook_expand_rallocx, ptr, oldusize, | ||
| 200 | usize, (uintptr_t)ptr, hook_args->args); | ||
| 201 | return edata_addr_get(edata); | ||
| 202 | } | ||
| 203 | |||
| 204 | /* | ||
| 205 | * usize and old size are different enough that we need to use a | ||
| 206 | * different size class. In that case, fall back to allocating new | ||
| 207 | * space and copying. | ||
| 208 | */ | ||
| 209 | void *ret = large_ralloc_move_helper(tsdn, arena, usize, alignment, | ||
| 210 | zero); | ||
| 211 | if (ret == NULL) { | ||
| 212 | return NULL; | ||
| 213 | } | ||
| 214 | |||
| 215 | hook_invoke_alloc(hook_args->is_realloc | ||
| 216 | ? hook_alloc_realloc : hook_alloc_rallocx, ret, (uintptr_t)ret, | ||
| 217 | hook_args->args); | ||
| 218 | hook_invoke_dalloc(hook_args->is_realloc | ||
| 219 | ? hook_dalloc_realloc : hook_dalloc_rallocx, ptr, hook_args->args); | ||
| 220 | |||
| 221 | size_t copysize = (usize < oldusize) ? usize : oldusize; | ||
| 222 | memcpy(ret, edata_addr_get(edata), copysize); | ||
| 223 | isdalloct(tsdn, edata_addr_get(edata), oldusize, tcache, NULL, true); | ||
| 224 | return ret; | ||
| 225 | } | ||
| 226 | |||
| 227 | /* | ||
| 228 | * locked indicates whether the arena's large_mtx is currently held. | ||
| 229 | */ | ||
| 230 | static void | ||
| 231 | large_dalloc_prep_impl(tsdn_t *tsdn, arena_t *arena, edata_t *edata, | ||
| 232 | bool locked) { | ||
| 233 | if (!locked) { | ||
| 234 | /* See comments in arena_bin_slabs_full_insert(). */ | ||
| 235 | if (!arena_is_auto(arena)) { | ||
| 236 | malloc_mutex_lock(tsdn, &arena->large_mtx); | ||
| 237 | edata_list_active_remove(&arena->large, edata); | ||
| 238 | malloc_mutex_unlock(tsdn, &arena->large_mtx); | ||
| 239 | } | ||
| 240 | } else { | ||
| 241 | /* Only hold the large_mtx if necessary. */ | ||
| 242 | if (!arena_is_auto(arena)) { | ||
| 243 | malloc_mutex_assert_owner(tsdn, &arena->large_mtx); | ||
| 244 | edata_list_active_remove(&arena->large, edata); | ||
| 245 | } | ||
| 246 | } | ||
| 247 | arena_extent_dalloc_large_prep(tsdn, arena, edata); | ||
| 248 | } | ||
| 249 | |||
| 250 | static void | ||
| 251 | large_dalloc_finish_impl(tsdn_t *tsdn, arena_t *arena, edata_t *edata) { | ||
| 252 | bool deferred_work_generated = false; | ||
| 253 | pa_dalloc(tsdn, &arena->pa_shard, edata, &deferred_work_generated); | ||
| 254 | if (deferred_work_generated) { | ||
| 255 | arena_handle_deferred_work(tsdn, arena); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | void | ||
| 260 | large_dalloc_prep_locked(tsdn_t *tsdn, edata_t *edata) { | ||
| 261 | large_dalloc_prep_impl(tsdn, arena_get_from_edata(edata), edata, true); | ||
| 262 | } | ||
| 263 | |||
| 264 | void | ||
| 265 | large_dalloc_finish(tsdn_t *tsdn, edata_t *edata) { | ||
| 266 | large_dalloc_finish_impl(tsdn, arena_get_from_edata(edata), edata); | ||
| 267 | } | ||
| 268 | |||
| 269 | void | ||
| 270 | large_dalloc(tsdn_t *tsdn, edata_t *edata) { | ||
| 271 | arena_t *arena = arena_get_from_edata(edata); | ||
| 272 | large_dalloc_prep_impl(tsdn, arena, edata, false); | ||
| 273 | large_dalloc_finish_impl(tsdn, arena, edata); | ||
| 274 | arena_decay_tick(tsdn, arena); | ||
| 275 | } | ||
| 276 | |||
| 277 | size_t | ||
| 278 | large_salloc(tsdn_t *tsdn, const edata_t *edata) { | ||
| 279 | return edata_usize_get(edata); | ||
| 280 | } | ||
| 281 | |||
| 282 | void | ||
| 283 | large_prof_info_get(tsd_t *tsd, edata_t *edata, prof_info_t *prof_info, | ||
| 284 | bool reset_recent) { | ||
| 285 | assert(prof_info != NULL); | ||
| 286 | |||
| 287 | prof_tctx_t *alloc_tctx = edata_prof_tctx_get(edata); | ||
| 288 | prof_info->alloc_tctx = alloc_tctx; | ||
| 289 | |||
| 290 | if ((uintptr_t)alloc_tctx > (uintptr_t)1U) { | ||
| 291 | nstime_copy(&prof_info->alloc_time, | ||
| 292 | edata_prof_alloc_time_get(edata)); | ||
| 293 | prof_info->alloc_size = edata_prof_alloc_size_get(edata); | ||
| 294 | if (reset_recent) { | ||
| 295 | /* | ||
| 296 | * Reset the pointer on the recent allocation record, | ||
| 297 | * so that this allocation is recorded as released. | ||
| 298 | */ | ||
| 299 | prof_recent_alloc_reset(tsd, edata); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | static void | ||
| 305 | large_prof_tctx_set(edata_t *edata, prof_tctx_t *tctx) { | ||
| 306 | edata_prof_tctx_set(edata, tctx); | ||
| 307 | } | ||
| 308 | |||
| 309 | void | ||
| 310 | large_prof_tctx_reset(edata_t *edata) { | ||
| 311 | large_prof_tctx_set(edata, (prof_tctx_t *)(uintptr_t)1U); | ||
| 312 | } | ||
| 313 | |||
| 314 | void | ||
| 315 | large_prof_info_set(edata_t *edata, prof_tctx_t *tctx, size_t size) { | ||
| 316 | nstime_t t; | ||
| 317 | nstime_prof_init_update(&t); | ||
| 318 | edata_prof_alloc_time_set(edata, &t); | ||
| 319 | edata_prof_alloc_size_set(edata, size); | ||
| 320 | edata_prof_recent_alloc_init(edata); | ||
| 321 | large_prof_tctx_set(edata, tctx); | ||
| 322 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/log.c b/examples/redis-unstable/deps/jemalloc/src/log.c deleted file mode 100644 index 778902f..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/log.c +++ /dev/null | |||
| @@ -1,78 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/log.h" | ||
| 5 | |||
| 6 | char log_var_names[JEMALLOC_LOG_VAR_BUFSIZE]; | ||
| 7 | atomic_b_t log_init_done = ATOMIC_INIT(false); | ||
| 8 | |||
| 9 | /* | ||
| 10 | * Returns true if we were able to pick out a segment. Fills in r_segment_end | ||
| 11 | * with a pointer to the first character after the end of the string. | ||
| 12 | */ | ||
| 13 | static const char * | ||
| 14 | log_var_extract_segment(const char* segment_begin) { | ||
| 15 | const char *end; | ||
| 16 | for (end = segment_begin; *end != '\0' && *end != '|'; end++) { | ||
| 17 | } | ||
| 18 | return end; | ||
| 19 | } | ||
| 20 | |||
| 21 | static bool | ||
| 22 | log_var_matches_segment(const char *segment_begin, const char *segment_end, | ||
| 23 | const char *log_var_begin, const char *log_var_end) { | ||
| 24 | assert(segment_begin <= segment_end); | ||
| 25 | assert(log_var_begin < log_var_end); | ||
| 26 | |||
| 27 | ptrdiff_t segment_len = segment_end - segment_begin; | ||
| 28 | ptrdiff_t log_var_len = log_var_end - log_var_begin; | ||
| 29 | /* The special '.' segment matches everything. */ | ||
| 30 | if (segment_len == 1 && *segment_begin == '.') { | ||
| 31 | return true; | ||
| 32 | } | ||
| 33 | if (segment_len == log_var_len) { | ||
| 34 | return strncmp(segment_begin, log_var_begin, segment_len) == 0; | ||
| 35 | } else if (segment_len < log_var_len) { | ||
| 36 | return strncmp(segment_begin, log_var_begin, segment_len) == 0 | ||
| 37 | && log_var_begin[segment_len] == '.'; | ||
| 38 | } else { | ||
| 39 | return false; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | unsigned | ||
| 44 | log_var_update_state(log_var_t *log_var) { | ||
| 45 | const char *log_var_begin = log_var->name; | ||
| 46 | const char *log_var_end = log_var->name + strlen(log_var->name); | ||
| 47 | |||
| 48 | /* Pointer to one before the beginning of the current segment. */ | ||
| 49 | const char *segment_begin = log_var_names; | ||
| 50 | |||
| 51 | /* | ||
| 52 | * If log_init done is false, we haven't parsed the malloc conf yet. To | ||
| 53 | * avoid log-spew, we default to not displaying anything. | ||
| 54 | */ | ||
| 55 | if (!atomic_load_b(&log_init_done, ATOMIC_ACQUIRE)) { | ||
| 56 | return LOG_INITIALIZED_NOT_ENABLED; | ||
| 57 | } | ||
| 58 | |||
| 59 | while (true) { | ||
| 60 | const char *segment_end = log_var_extract_segment( | ||
| 61 | segment_begin); | ||
| 62 | assert(segment_end < log_var_names + JEMALLOC_LOG_VAR_BUFSIZE); | ||
| 63 | if (log_var_matches_segment(segment_begin, segment_end, | ||
| 64 | log_var_begin, log_var_end)) { | ||
| 65 | atomic_store_u(&log_var->state, LOG_ENABLED, | ||
| 66 | ATOMIC_RELAXED); | ||
| 67 | return LOG_ENABLED; | ||
| 68 | } | ||
| 69 | if (*segment_end == '\0') { | ||
| 70 | /* Hit the end of the segment string with no match. */ | ||
| 71 | atomic_store_u(&log_var->state, | ||
| 72 | LOG_INITIALIZED_NOT_ENABLED, ATOMIC_RELAXED); | ||
| 73 | return LOG_INITIALIZED_NOT_ENABLED; | ||
| 74 | } | ||
| 75 | /* Otherwise, skip the delimiter and continue. */ | ||
| 76 | segment_begin = segment_end + 1; | ||
| 77 | } | ||
| 78 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/malloc_io.c b/examples/redis-unstable/deps/jemalloc/src/malloc_io.c deleted file mode 100644 index b76885c..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/malloc_io.c +++ /dev/null | |||
| @@ -1,697 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/malloc_io.h" | ||
| 5 | #include "jemalloc/internal/util.h" | ||
| 6 | |||
| 7 | #ifdef assert | ||
| 8 | # undef assert | ||
| 9 | #endif | ||
| 10 | #ifdef not_reached | ||
| 11 | # undef not_reached | ||
| 12 | #endif | ||
| 13 | #ifdef not_implemented | ||
| 14 | # undef not_implemented | ||
| 15 | #endif | ||
| 16 | #ifdef assert_not_implemented | ||
| 17 | # undef assert_not_implemented | ||
| 18 | #endif | ||
| 19 | |||
| 20 | /* | ||
| 21 | * Define simple versions of assertion macros that won't recurse in case | ||
| 22 | * of assertion failures in malloc_*printf(). | ||
| 23 | */ | ||
| 24 | #define assert(e) do { \ | ||
| 25 | if (config_debug && !(e)) { \ | ||
| 26 | malloc_write("<jemalloc>: Failed assertion\n"); \ | ||
| 27 | abort(); \ | ||
| 28 | } \ | ||
| 29 | } while (0) | ||
| 30 | |||
| 31 | #define not_reached() do { \ | ||
| 32 | if (config_debug) { \ | ||
| 33 | malloc_write("<jemalloc>: Unreachable code reached\n"); \ | ||
| 34 | abort(); \ | ||
| 35 | } \ | ||
| 36 | unreachable(); \ | ||
| 37 | } while (0) | ||
| 38 | |||
| 39 | #define not_implemented() do { \ | ||
| 40 | if (config_debug) { \ | ||
| 41 | malloc_write("<jemalloc>: Not implemented\n"); \ | ||
| 42 | abort(); \ | ||
| 43 | } \ | ||
| 44 | } while (0) | ||
| 45 | |||
| 46 | #define assert_not_implemented(e) do { \ | ||
| 47 | if (unlikely(config_debug && !(e))) { \ | ||
| 48 | not_implemented(); \ | ||
| 49 | } \ | ||
| 50 | } while (0) | ||
| 51 | |||
| 52 | /******************************************************************************/ | ||
| 53 | /* Function prototypes for non-inline static functions. */ | ||
| 54 | |||
| 55 | #define U2S_BUFSIZE ((1U << (LG_SIZEOF_INTMAX_T + 3)) + 1) | ||
| 56 | static char *u2s(uintmax_t x, unsigned base, bool uppercase, char *s, | ||
| 57 | size_t *slen_p); | ||
| 58 | #define D2S_BUFSIZE (1 + U2S_BUFSIZE) | ||
| 59 | static char *d2s(intmax_t x, char sign, char *s, size_t *slen_p); | ||
| 60 | #define O2S_BUFSIZE (1 + U2S_BUFSIZE) | ||
| 61 | static char *o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p); | ||
| 62 | #define X2S_BUFSIZE (2 + U2S_BUFSIZE) | ||
| 63 | static char *x2s(uintmax_t x, bool alt_form, bool uppercase, char *s, | ||
| 64 | size_t *slen_p); | ||
| 65 | |||
| 66 | /******************************************************************************/ | ||
| 67 | |||
| 68 | /* malloc_message() setup. */ | ||
| 69 | void | ||
| 70 | wrtmessage(void *cbopaque, const char *s) { | ||
| 71 | malloc_write_fd(STDERR_FILENO, s, strlen(s)); | ||
| 72 | } | ||
| 73 | |||
| 74 | JEMALLOC_EXPORT void (*je_malloc_message)(void *, const char *s); | ||
| 75 | |||
| 76 | /* | ||
| 77 | * Wrapper around malloc_message() that avoids the need for | ||
| 78 | * je_malloc_message(...) throughout the code. | ||
| 79 | */ | ||
| 80 | void | ||
| 81 | malloc_write(const char *s) { | ||
| 82 | if (je_malloc_message != NULL) { | ||
| 83 | je_malloc_message(NULL, s); | ||
| 84 | } else { | ||
| 85 | wrtmessage(NULL, s); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | /* | ||
| 90 | * glibc provides a non-standard strerror_r() when _GNU_SOURCE is defined, so | ||
| 91 | * provide a wrapper. | ||
| 92 | */ | ||
| 93 | int | ||
| 94 | buferror(int err, char *buf, size_t buflen) { | ||
| 95 | #ifdef _WIN32 | ||
| 96 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, | ||
| 97 | (LPSTR)buf, (DWORD)buflen, NULL); | ||
| 98 | return 0; | ||
| 99 | #elif defined(JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE) && defined(_GNU_SOURCE) | ||
| 100 | char *b = strerror_r(err, buf, buflen); | ||
| 101 | if (b != buf) { | ||
| 102 | strncpy(buf, b, buflen); | ||
| 103 | buf[buflen-1] = '\0'; | ||
| 104 | } | ||
| 105 | return 0; | ||
| 106 | #else | ||
| 107 | return strerror_r(err, buf, buflen); | ||
| 108 | #endif | ||
| 109 | } | ||
| 110 | |||
| 111 | uintmax_t | ||
| 112 | malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base) { | ||
| 113 | uintmax_t ret, digit; | ||
| 114 | unsigned b; | ||
| 115 | bool neg; | ||
| 116 | const char *p, *ns; | ||
| 117 | |||
| 118 | p = nptr; | ||
| 119 | if (base < 0 || base == 1 || base > 36) { | ||
| 120 | ns = p; | ||
| 121 | set_errno(EINVAL); | ||
| 122 | ret = UINTMAX_MAX; | ||
| 123 | goto label_return; | ||
| 124 | } | ||
| 125 | b = base; | ||
| 126 | |||
| 127 | /* Swallow leading whitespace and get sign, if any. */ | ||
| 128 | neg = false; | ||
| 129 | while (true) { | ||
| 130 | switch (*p) { | ||
| 131 | case '\t': case '\n': case '\v': case '\f': case '\r': case ' ': | ||
| 132 | p++; | ||
| 133 | break; | ||
| 134 | case '-': | ||
| 135 | neg = true; | ||
| 136 | JEMALLOC_FALLTHROUGH; | ||
| 137 | case '+': | ||
| 138 | p++; | ||
| 139 | JEMALLOC_FALLTHROUGH; | ||
| 140 | default: | ||
| 141 | goto label_prefix; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | /* Get prefix, if any. */ | ||
| 146 | label_prefix: | ||
| 147 | /* | ||
| 148 | * Note where the first non-whitespace/sign character is so that it is | ||
| 149 | * possible to tell whether any digits are consumed (e.g., " 0" vs. | ||
| 150 | * " -x"). | ||
| 151 | */ | ||
| 152 | ns = p; | ||
| 153 | if (*p == '0') { | ||
| 154 | switch (p[1]) { | ||
| 155 | case '0': case '1': case '2': case '3': case '4': case '5': | ||
| 156 | case '6': case '7': | ||
| 157 | if (b == 0) { | ||
| 158 | b = 8; | ||
| 159 | } | ||
| 160 | if (b == 8) { | ||
| 161 | p++; | ||
| 162 | } | ||
| 163 | break; | ||
| 164 | case 'X': case 'x': | ||
| 165 | switch (p[2]) { | ||
| 166 | case '0': case '1': case '2': case '3': case '4': | ||
| 167 | case '5': case '6': case '7': case '8': case '9': | ||
| 168 | case 'A': case 'B': case 'C': case 'D': case 'E': | ||
| 169 | case 'F': | ||
| 170 | case 'a': case 'b': case 'c': case 'd': case 'e': | ||
| 171 | case 'f': | ||
| 172 | if (b == 0) { | ||
| 173 | b = 16; | ||
| 174 | } | ||
| 175 | if (b == 16) { | ||
| 176 | p += 2; | ||
| 177 | } | ||
| 178 | break; | ||
| 179 | default: | ||
| 180 | break; | ||
| 181 | } | ||
| 182 | break; | ||
| 183 | default: | ||
| 184 | p++; | ||
| 185 | ret = 0; | ||
| 186 | goto label_return; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | if (b == 0) { | ||
| 190 | b = 10; | ||
| 191 | } | ||
| 192 | |||
| 193 | /* Convert. */ | ||
| 194 | ret = 0; | ||
| 195 | while ((*p >= '0' && *p <= '9' && (digit = *p - '0') < b) | ||
| 196 | || (*p >= 'A' && *p <= 'Z' && (digit = 10 + *p - 'A') < b) | ||
| 197 | || (*p >= 'a' && *p <= 'z' && (digit = 10 + *p - 'a') < b)) { | ||
| 198 | uintmax_t pret = ret; | ||
| 199 | ret *= b; | ||
| 200 | ret += digit; | ||
| 201 | if (ret < pret) { | ||
| 202 | /* Overflow. */ | ||
| 203 | set_errno(ERANGE); | ||
| 204 | ret = UINTMAX_MAX; | ||
| 205 | goto label_return; | ||
| 206 | } | ||
| 207 | p++; | ||
| 208 | } | ||
| 209 | if (neg) { | ||
| 210 | ret = (uintmax_t)(-((intmax_t)ret)); | ||
| 211 | } | ||
| 212 | |||
| 213 | if (p == ns) { | ||
| 214 | /* No conversion performed. */ | ||
| 215 | set_errno(EINVAL); | ||
| 216 | ret = UINTMAX_MAX; | ||
| 217 | goto label_return; | ||
| 218 | } | ||
| 219 | |||
| 220 | label_return: | ||
| 221 | if (endptr != NULL) { | ||
| 222 | if (p == ns) { | ||
| 223 | /* No characters were converted. */ | ||
| 224 | *endptr = (char *)nptr; | ||
| 225 | } else { | ||
| 226 | *endptr = (char *)p; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | return ret; | ||
| 230 | } | ||
| 231 | |||
| 232 | static char * | ||
| 233 | u2s(uintmax_t x, unsigned base, bool uppercase, char *s, size_t *slen_p) { | ||
| 234 | unsigned i; | ||
| 235 | |||
| 236 | i = U2S_BUFSIZE - 1; | ||
| 237 | s[i] = '\0'; | ||
| 238 | switch (base) { | ||
| 239 | case 10: | ||
| 240 | do { | ||
| 241 | i--; | ||
| 242 | s[i] = "0123456789"[x % (uint64_t)10]; | ||
| 243 | x /= (uint64_t)10; | ||
| 244 | } while (x > 0); | ||
| 245 | break; | ||
| 246 | case 16: { | ||
| 247 | const char *digits = (uppercase) | ||
| 248 | ? "0123456789ABCDEF" | ||
| 249 | : "0123456789abcdef"; | ||
| 250 | |||
| 251 | do { | ||
| 252 | i--; | ||
| 253 | s[i] = digits[x & 0xf]; | ||
| 254 | x >>= 4; | ||
| 255 | } while (x > 0); | ||
| 256 | break; | ||
| 257 | } default: { | ||
| 258 | const char *digits = (uppercase) | ||
| 259 | ? "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
| 260 | : "0123456789abcdefghijklmnopqrstuvwxyz"; | ||
| 261 | |||
| 262 | assert(base >= 2 && base <= 36); | ||
| 263 | do { | ||
| 264 | i--; | ||
| 265 | s[i] = digits[x % (uint64_t)base]; | ||
| 266 | x /= (uint64_t)base; | ||
| 267 | } while (x > 0); | ||
| 268 | }} | ||
| 269 | |||
| 270 | *slen_p = U2S_BUFSIZE - 1 - i; | ||
| 271 | return &s[i]; | ||
| 272 | } | ||
| 273 | |||
| 274 | static char * | ||
| 275 | d2s(intmax_t x, char sign, char *s, size_t *slen_p) { | ||
| 276 | bool neg; | ||
| 277 | |||
| 278 | if ((neg = (x < 0))) { | ||
| 279 | x = -x; | ||
| 280 | } | ||
| 281 | s = u2s(x, 10, false, s, slen_p); | ||
| 282 | if (neg) { | ||
| 283 | sign = '-'; | ||
| 284 | } | ||
| 285 | switch (sign) { | ||
| 286 | case '-': | ||
| 287 | if (!neg) { | ||
| 288 | break; | ||
| 289 | } | ||
| 290 | JEMALLOC_FALLTHROUGH; | ||
| 291 | case ' ': | ||
| 292 | case '+': | ||
| 293 | s--; | ||
| 294 | (*slen_p)++; | ||
| 295 | *s = sign; | ||
| 296 | break; | ||
| 297 | default: not_reached(); | ||
| 298 | } | ||
| 299 | return s; | ||
| 300 | } | ||
| 301 | |||
| 302 | static char * | ||
| 303 | o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p) { | ||
| 304 | s = u2s(x, 8, false, s, slen_p); | ||
| 305 | if (alt_form && *s != '0') { | ||
| 306 | s--; | ||
| 307 | (*slen_p)++; | ||
| 308 | *s = '0'; | ||
| 309 | } | ||
| 310 | return s; | ||
| 311 | } | ||
| 312 | |||
| 313 | static char * | ||
| 314 | x2s(uintmax_t x, bool alt_form, bool uppercase, char *s, size_t *slen_p) { | ||
| 315 | s = u2s(x, 16, uppercase, s, slen_p); | ||
| 316 | if (alt_form) { | ||
| 317 | s -= 2; | ||
| 318 | (*slen_p) += 2; | ||
| 319 | memcpy(s, uppercase ? "0X" : "0x", 2); | ||
| 320 | } | ||
| 321 | return s; | ||
| 322 | } | ||
| 323 | |||
| 324 | JEMALLOC_COLD | ||
| 325 | size_t | ||
| 326 | malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap) { | ||
| 327 | size_t i; | ||
| 328 | const char *f; | ||
| 329 | |||
| 330 | #define APPEND_C(c) do { \ | ||
| 331 | if (i < size) { \ | ||
| 332 | str[i] = (c); \ | ||
| 333 | } \ | ||
| 334 | i++; \ | ||
| 335 | } while (0) | ||
| 336 | #define APPEND_S(s, slen) do { \ | ||
| 337 | if (i < size) { \ | ||
| 338 | size_t cpylen = (slen <= size - i) ? slen : size - i; \ | ||
| 339 | memcpy(&str[i], s, cpylen); \ | ||
| 340 | } \ | ||
| 341 | i += slen; \ | ||
| 342 | } while (0) | ||
| 343 | #define APPEND_PADDED_S(s, slen, width, left_justify) do { \ | ||
| 344 | /* Left padding. */ \ | ||
| 345 | size_t pad_len = (width == -1) ? 0 : ((slen < (size_t)width) ? \ | ||
| 346 | (size_t)width - slen : 0); \ | ||
| 347 | if (!left_justify && pad_len != 0) { \ | ||
| 348 | size_t j; \ | ||
| 349 | for (j = 0; j < pad_len; j++) { \ | ||
| 350 | if (pad_zero) { \ | ||
| 351 | APPEND_C('0'); \ | ||
| 352 | } else { \ | ||
| 353 | APPEND_C(' '); \ | ||
| 354 | } \ | ||
| 355 | } \ | ||
| 356 | } \ | ||
| 357 | /* Value. */ \ | ||
| 358 | APPEND_S(s, slen); \ | ||
| 359 | /* Right padding. */ \ | ||
| 360 | if (left_justify && pad_len != 0) { \ | ||
| 361 | size_t j; \ | ||
| 362 | for (j = 0; j < pad_len; j++) { \ | ||
| 363 | APPEND_C(' '); \ | ||
| 364 | } \ | ||
| 365 | } \ | ||
| 366 | } while (0) | ||
| 367 | #define GET_ARG_NUMERIC(val, len) do { \ | ||
| 368 | switch ((unsigned char)len) { \ | ||
| 369 | case '?': \ | ||
| 370 | val = va_arg(ap, int); \ | ||
| 371 | break; \ | ||
| 372 | case '?' | 0x80: \ | ||
| 373 | val = va_arg(ap, unsigned int); \ | ||
| 374 | break; \ | ||
| 375 | case 'l': \ | ||
| 376 | val = va_arg(ap, long); \ | ||
| 377 | break; \ | ||
| 378 | case 'l' | 0x80: \ | ||
| 379 | val = va_arg(ap, unsigned long); \ | ||
| 380 | break; \ | ||
| 381 | case 'q': \ | ||
| 382 | val = va_arg(ap, long long); \ | ||
| 383 | break; \ | ||
| 384 | case 'q' | 0x80: \ | ||
| 385 | val = va_arg(ap, unsigned long long); \ | ||
| 386 | break; \ | ||
| 387 | case 'j': \ | ||
| 388 | val = va_arg(ap, intmax_t); \ | ||
| 389 | break; \ | ||
| 390 | case 'j' | 0x80: \ | ||
| 391 | val = va_arg(ap, uintmax_t); \ | ||
| 392 | break; \ | ||
| 393 | case 't': \ | ||
| 394 | val = va_arg(ap, ptrdiff_t); \ | ||
| 395 | break; \ | ||
| 396 | case 'z': \ | ||
| 397 | val = va_arg(ap, ssize_t); \ | ||
| 398 | break; \ | ||
| 399 | case 'z' | 0x80: \ | ||
| 400 | val = va_arg(ap, size_t); \ | ||
| 401 | break; \ | ||
| 402 | case 'p': /* Synthetic; used for %p. */ \ | ||
| 403 | val = va_arg(ap, uintptr_t); \ | ||
| 404 | break; \ | ||
| 405 | default: \ | ||
| 406 | not_reached(); \ | ||
| 407 | val = 0; \ | ||
| 408 | } \ | ||
| 409 | } while (0) | ||
| 410 | |||
| 411 | i = 0; | ||
| 412 | f = format; | ||
| 413 | while (true) { | ||
| 414 | switch (*f) { | ||
| 415 | case '\0': goto label_out; | ||
| 416 | case '%': { | ||
| 417 | bool alt_form = false; | ||
| 418 | bool left_justify = false; | ||
| 419 | bool plus_space = false; | ||
| 420 | bool plus_plus = false; | ||
| 421 | int prec = -1; | ||
| 422 | int width = -1; | ||
| 423 | unsigned char len = '?'; | ||
| 424 | char *s; | ||
| 425 | size_t slen; | ||
| 426 | bool first_width_digit = true; | ||
| 427 | bool pad_zero = false; | ||
| 428 | |||
| 429 | f++; | ||
| 430 | /* Flags. */ | ||
| 431 | while (true) { | ||
| 432 | switch (*f) { | ||
| 433 | case '#': | ||
| 434 | assert(!alt_form); | ||
| 435 | alt_form = true; | ||
| 436 | break; | ||
| 437 | case '-': | ||
| 438 | assert(!left_justify); | ||
| 439 | left_justify = true; | ||
| 440 | break; | ||
| 441 | case ' ': | ||
| 442 | assert(!plus_space); | ||
| 443 | plus_space = true; | ||
| 444 | break; | ||
| 445 | case '+': | ||
| 446 | assert(!plus_plus); | ||
| 447 | plus_plus = true; | ||
| 448 | break; | ||
| 449 | default: goto label_width; | ||
| 450 | } | ||
| 451 | f++; | ||
| 452 | } | ||
| 453 | /* Width. */ | ||
| 454 | label_width: | ||
| 455 | switch (*f) { | ||
| 456 | case '*': | ||
| 457 | width = va_arg(ap, int); | ||
| 458 | f++; | ||
| 459 | if (width < 0) { | ||
| 460 | left_justify = true; | ||
| 461 | width = -width; | ||
| 462 | } | ||
| 463 | break; | ||
| 464 | case '0': | ||
| 465 | if (first_width_digit) { | ||
| 466 | pad_zero = true; | ||
| 467 | } | ||
| 468 | JEMALLOC_FALLTHROUGH; | ||
| 469 | case '1': case '2': case '3': case '4': | ||
| 470 | case '5': case '6': case '7': case '8': case '9': { | ||
| 471 | uintmax_t uwidth; | ||
| 472 | set_errno(0); | ||
| 473 | uwidth = malloc_strtoumax(f, (char **)&f, 10); | ||
| 474 | assert(uwidth != UINTMAX_MAX || get_errno() != | ||
| 475 | ERANGE); | ||
| 476 | width = (int)uwidth; | ||
| 477 | first_width_digit = false; | ||
| 478 | break; | ||
| 479 | } default: | ||
| 480 | break; | ||
| 481 | } | ||
| 482 | /* Width/precision separator. */ | ||
| 483 | if (*f == '.') { | ||
| 484 | f++; | ||
| 485 | } else { | ||
| 486 | goto label_length; | ||
| 487 | } | ||
| 488 | /* Precision. */ | ||
| 489 | switch (*f) { | ||
| 490 | case '*': | ||
| 491 | prec = va_arg(ap, int); | ||
| 492 | f++; | ||
| 493 | break; | ||
| 494 | case '0': case '1': case '2': case '3': case '4': | ||
| 495 | case '5': case '6': case '7': case '8': case '9': { | ||
| 496 | uintmax_t uprec; | ||
| 497 | set_errno(0); | ||
| 498 | uprec = malloc_strtoumax(f, (char **)&f, 10); | ||
| 499 | assert(uprec != UINTMAX_MAX || get_errno() != | ||
| 500 | ERANGE); | ||
| 501 | prec = (int)uprec; | ||
| 502 | break; | ||
| 503 | } | ||
| 504 | default: break; | ||
| 505 | } | ||
| 506 | /* Length. */ | ||
| 507 | label_length: | ||
| 508 | switch (*f) { | ||
| 509 | case 'l': | ||
| 510 | f++; | ||
| 511 | if (*f == 'l') { | ||
| 512 | len = 'q'; | ||
| 513 | f++; | ||
| 514 | } else { | ||
| 515 | len = 'l'; | ||
| 516 | } | ||
| 517 | break; | ||
| 518 | case 'q': case 'j': case 't': case 'z': | ||
| 519 | len = *f; | ||
| 520 | f++; | ||
| 521 | break; | ||
| 522 | default: break; | ||
| 523 | } | ||
| 524 | /* Conversion specifier. */ | ||
| 525 | switch (*f) { | ||
| 526 | case '%': | ||
| 527 | /* %% */ | ||
| 528 | APPEND_C(*f); | ||
| 529 | f++; | ||
| 530 | break; | ||
| 531 | case 'd': case 'i': { | ||
| 532 | intmax_t val JEMALLOC_CC_SILENCE_INIT(0); | ||
| 533 | char buf[D2S_BUFSIZE]; | ||
| 534 | |||
| 535 | /* | ||
| 536 | * Outputting negative, zero-padded numbers | ||
| 537 | * would require a nontrivial rework of the | ||
| 538 | * interaction between the width and padding | ||
| 539 | * (since 0 padding goes between the '-' and the | ||
| 540 | * number, while ' ' padding goes either before | ||
| 541 | * the - or after the number. Since we | ||
| 542 | * currently don't ever need 0-padded negative | ||
| 543 | * numbers, just don't bother supporting it. | ||
| 544 | */ | ||
| 545 | assert(!pad_zero); | ||
| 546 | |||
| 547 | GET_ARG_NUMERIC(val, len); | ||
| 548 | s = d2s(val, (plus_plus ? '+' : (plus_space ? | ||
| 549 | ' ' : '-')), buf, &slen); | ||
| 550 | APPEND_PADDED_S(s, slen, width, left_justify); | ||
| 551 | f++; | ||
| 552 | break; | ||
| 553 | } case 'o': { | ||
| 554 | uintmax_t val JEMALLOC_CC_SILENCE_INIT(0); | ||
| 555 | char buf[O2S_BUFSIZE]; | ||
| 556 | |||
| 557 | GET_ARG_NUMERIC(val, len | 0x80); | ||
| 558 | s = o2s(val, alt_form, buf, &slen); | ||
| 559 | APPEND_PADDED_S(s, slen, width, left_justify); | ||
| 560 | f++; | ||
| 561 | break; | ||
| 562 | } case 'u': { | ||
| 563 | uintmax_t val JEMALLOC_CC_SILENCE_INIT(0); | ||
| 564 | char buf[U2S_BUFSIZE]; | ||
| 565 | |||
| 566 | GET_ARG_NUMERIC(val, len | 0x80); | ||
| 567 | s = u2s(val, 10, false, buf, &slen); | ||
| 568 | APPEND_PADDED_S(s, slen, width, left_justify); | ||
| 569 | f++; | ||
| 570 | break; | ||
| 571 | } case 'x': case 'X': { | ||
| 572 | uintmax_t val JEMALLOC_CC_SILENCE_INIT(0); | ||
| 573 | char buf[X2S_BUFSIZE]; | ||
| 574 | |||
| 575 | GET_ARG_NUMERIC(val, len | 0x80); | ||
| 576 | s = x2s(val, alt_form, *f == 'X', buf, &slen); | ||
| 577 | APPEND_PADDED_S(s, slen, width, left_justify); | ||
| 578 | f++; | ||
| 579 | break; | ||
| 580 | } case 'c': { | ||
| 581 | unsigned char val; | ||
| 582 | char buf[2]; | ||
| 583 | |||
| 584 | assert(len == '?' || len == 'l'); | ||
| 585 | assert_not_implemented(len != 'l'); | ||
| 586 | val = va_arg(ap, int); | ||
| 587 | buf[0] = val; | ||
| 588 | buf[1] = '\0'; | ||
| 589 | APPEND_PADDED_S(buf, 1, width, left_justify); | ||
| 590 | f++; | ||
| 591 | break; | ||
| 592 | } case 's': | ||
| 593 | assert(len == '?' || len == 'l'); | ||
| 594 | assert_not_implemented(len != 'l'); | ||
| 595 | s = va_arg(ap, char *); | ||
| 596 | slen = (prec < 0) ? strlen(s) : (size_t)prec; | ||
| 597 | APPEND_PADDED_S(s, slen, width, left_justify); | ||
| 598 | f++; | ||
| 599 | break; | ||
| 600 | case 'p': { | ||
| 601 | uintmax_t val; | ||
| 602 | char buf[X2S_BUFSIZE]; | ||
| 603 | |||
| 604 | GET_ARG_NUMERIC(val, 'p'); | ||
| 605 | s = x2s(val, true, false, buf, &slen); | ||
| 606 | APPEND_PADDED_S(s, slen, width, left_justify); | ||
| 607 | f++; | ||
| 608 | break; | ||
| 609 | } default: not_reached(); | ||
| 610 | } | ||
| 611 | break; | ||
| 612 | } default: { | ||
| 613 | APPEND_C(*f); | ||
| 614 | f++; | ||
| 615 | break; | ||
| 616 | }} | ||
| 617 | } | ||
| 618 | label_out: | ||
| 619 | if (i < size) { | ||
| 620 | str[i] = '\0'; | ||
| 621 | } else { | ||
| 622 | str[size - 1] = '\0'; | ||
| 623 | } | ||
| 624 | |||
| 625 | #undef APPEND_C | ||
| 626 | #undef APPEND_S | ||
| 627 | #undef APPEND_PADDED_S | ||
| 628 | #undef GET_ARG_NUMERIC | ||
| 629 | return i; | ||
| 630 | } | ||
| 631 | |||
| 632 | JEMALLOC_FORMAT_PRINTF(3, 4) | ||
| 633 | size_t | ||
| 634 | malloc_snprintf(char *str, size_t size, const char *format, ...) { | ||
| 635 | size_t ret; | ||
| 636 | va_list ap; | ||
| 637 | |||
| 638 | va_start(ap, format); | ||
| 639 | ret = malloc_vsnprintf(str, size, format, ap); | ||
| 640 | va_end(ap); | ||
| 641 | |||
| 642 | return ret; | ||
| 643 | } | ||
| 644 | |||
| 645 | void | ||
| 646 | malloc_vcprintf(write_cb_t *write_cb, void *cbopaque, const char *format, | ||
| 647 | va_list ap) { | ||
| 648 | char buf[MALLOC_PRINTF_BUFSIZE]; | ||
| 649 | |||
| 650 | if (write_cb == NULL) { | ||
| 651 | /* | ||
| 652 | * The caller did not provide an alternate write_cb callback | ||
| 653 | * function, so use the default one. malloc_write() is an | ||
| 654 | * inline function, so use malloc_message() directly here. | ||
| 655 | */ | ||
| 656 | write_cb = (je_malloc_message != NULL) ? je_malloc_message : | ||
| 657 | wrtmessage; | ||
| 658 | } | ||
| 659 | |||
| 660 | malloc_vsnprintf(buf, sizeof(buf), format, ap); | ||
| 661 | write_cb(cbopaque, buf); | ||
| 662 | } | ||
| 663 | |||
| 664 | /* | ||
| 665 | * Print to a callback function in such a way as to (hopefully) avoid memory | ||
| 666 | * allocation. | ||
| 667 | */ | ||
| 668 | JEMALLOC_FORMAT_PRINTF(3, 4) | ||
| 669 | void | ||
| 670 | malloc_cprintf(write_cb_t *write_cb, void *cbopaque, const char *format, ...) { | ||
| 671 | va_list ap; | ||
| 672 | |||
| 673 | va_start(ap, format); | ||
| 674 | malloc_vcprintf(write_cb, cbopaque, format, ap); | ||
| 675 | va_end(ap); | ||
| 676 | } | ||
| 677 | |||
| 678 | /* Print to stderr in such a way as to avoid memory allocation. */ | ||
| 679 | JEMALLOC_FORMAT_PRINTF(1, 2) | ||
| 680 | void | ||
| 681 | malloc_printf(const char *format, ...) { | ||
| 682 | va_list ap; | ||
| 683 | |||
| 684 | va_start(ap, format); | ||
| 685 | malloc_vcprintf(NULL, NULL, format, ap); | ||
| 686 | va_end(ap); | ||
| 687 | } | ||
| 688 | |||
| 689 | /* | ||
| 690 | * Restore normal assertion macros, in order to make it possible to compile all | ||
| 691 | * C files as a single concatenation. | ||
| 692 | */ | ||
| 693 | #undef assert | ||
| 694 | #undef not_reached | ||
| 695 | #undef not_implemented | ||
| 696 | #undef assert_not_implemented | ||
| 697 | #include "jemalloc/internal/assert.h" | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/mutex.c b/examples/redis-unstable/deps/jemalloc/src/mutex.c deleted file mode 100644 index 0b3547a..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/mutex.c +++ /dev/null | |||
| @@ -1,228 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/malloc_io.h" | ||
| 6 | #include "jemalloc/internal/spin.h" | ||
| 7 | |||
| 8 | #ifndef _CRT_SPINCOUNT | ||
| 9 | #define _CRT_SPINCOUNT 4000 | ||
| 10 | #endif | ||
| 11 | |||
| 12 | /* | ||
| 13 | * Based on benchmark results, a fixed spin with this amount of retries works | ||
| 14 | * well for our critical sections. | ||
| 15 | */ | ||
| 16 | int64_t opt_mutex_max_spin = 600; | ||
| 17 | |||
| 18 | /******************************************************************************/ | ||
| 19 | /* Data. */ | ||
| 20 | |||
| 21 | #ifdef JEMALLOC_LAZY_LOCK | ||
| 22 | bool isthreaded = false; | ||
| 23 | #endif | ||
| 24 | #ifdef JEMALLOC_MUTEX_INIT_CB | ||
| 25 | static bool postpone_init = true; | ||
| 26 | static malloc_mutex_t *postponed_mutexes = NULL; | ||
| 27 | #endif | ||
| 28 | |||
| 29 | /******************************************************************************/ | ||
| 30 | /* | ||
| 31 | * We intercept pthread_create() calls in order to toggle isthreaded if the | ||
| 32 | * process goes multi-threaded. | ||
| 33 | */ | ||
| 34 | |||
| 35 | #if defined(JEMALLOC_LAZY_LOCK) && !defined(_WIN32) | ||
| 36 | JEMALLOC_EXPORT int | ||
| 37 | pthread_create(pthread_t *__restrict thread, | ||
| 38 | const pthread_attr_t *__restrict attr, void *(*start_routine)(void *), | ||
| 39 | void *__restrict arg) { | ||
| 40 | return pthread_create_wrapper(thread, attr, start_routine, arg); | ||
| 41 | } | ||
| 42 | #endif | ||
| 43 | |||
| 44 | /******************************************************************************/ | ||
| 45 | |||
| 46 | #ifdef JEMALLOC_MUTEX_INIT_CB | ||
| 47 | JEMALLOC_EXPORT int _pthread_mutex_init_calloc_cb(pthread_mutex_t *mutex, | ||
| 48 | void *(calloc_cb)(size_t, size_t)); | ||
| 49 | #endif | ||
| 50 | |||
| 51 | void | ||
| 52 | malloc_mutex_lock_slow(malloc_mutex_t *mutex) { | ||
| 53 | mutex_prof_data_t *data = &mutex->prof_data; | ||
| 54 | nstime_t before; | ||
| 55 | |||
| 56 | if (ncpus == 1) { | ||
| 57 | goto label_spin_done; | ||
| 58 | } | ||
| 59 | |||
| 60 | int cnt = 0; | ||
| 61 | do { | ||
| 62 | spin_cpu_spinwait(); | ||
| 63 | if (!atomic_load_b(&mutex->locked, ATOMIC_RELAXED) | ||
| 64 | && !malloc_mutex_trylock_final(mutex)) { | ||
| 65 | data->n_spin_acquired++; | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | } while (cnt++ < opt_mutex_max_spin || opt_mutex_max_spin == -1); | ||
| 69 | |||
| 70 | if (!config_stats) { | ||
| 71 | /* Only spin is useful when stats is off. */ | ||
| 72 | malloc_mutex_lock_final(mutex); | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | label_spin_done: | ||
| 76 | nstime_init_update(&before); | ||
| 77 | /* Copy before to after to avoid clock skews. */ | ||
| 78 | nstime_t after; | ||
| 79 | nstime_copy(&after, &before); | ||
| 80 | uint32_t n_thds = atomic_fetch_add_u32(&data->n_waiting_thds, 1, | ||
| 81 | ATOMIC_RELAXED) + 1; | ||
| 82 | /* One last try as above two calls may take quite some cycles. */ | ||
| 83 | if (!malloc_mutex_trylock_final(mutex)) { | ||
| 84 | atomic_fetch_sub_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED); | ||
| 85 | data->n_spin_acquired++; | ||
| 86 | return; | ||
| 87 | } | ||
| 88 | |||
| 89 | /* True slow path. */ | ||
| 90 | malloc_mutex_lock_final(mutex); | ||
| 91 | /* Update more slow-path only counters. */ | ||
| 92 | atomic_fetch_sub_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED); | ||
| 93 | nstime_update(&after); | ||
| 94 | |||
| 95 | nstime_t delta; | ||
| 96 | nstime_copy(&delta, &after); | ||
| 97 | nstime_subtract(&delta, &before); | ||
| 98 | |||
| 99 | data->n_wait_times++; | ||
| 100 | nstime_add(&data->tot_wait_time, &delta); | ||
| 101 | if (nstime_compare(&data->max_wait_time, &delta) < 0) { | ||
| 102 | nstime_copy(&data->max_wait_time, &delta); | ||
| 103 | } | ||
| 104 | if (n_thds > data->max_n_thds) { | ||
| 105 | data->max_n_thds = n_thds; | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | static void | ||
| 110 | mutex_prof_data_init(mutex_prof_data_t *data) { | ||
| 111 | memset(data, 0, sizeof(mutex_prof_data_t)); | ||
| 112 | nstime_init_zero(&data->max_wait_time); | ||
| 113 | nstime_init_zero(&data->tot_wait_time); | ||
| 114 | data->prev_owner = NULL; | ||
| 115 | } | ||
| 116 | |||
| 117 | void | ||
| 118 | malloc_mutex_prof_data_reset(tsdn_t *tsdn, malloc_mutex_t *mutex) { | ||
| 119 | malloc_mutex_assert_owner(tsdn, mutex); | ||
| 120 | mutex_prof_data_init(&mutex->prof_data); | ||
| 121 | } | ||
| 122 | |||
| 123 | static int | ||
| 124 | mutex_addr_comp(const witness_t *witness1, void *mutex1, | ||
| 125 | const witness_t *witness2, void *mutex2) { | ||
| 126 | assert(mutex1 != NULL); | ||
| 127 | assert(mutex2 != NULL); | ||
| 128 | uintptr_t mu1int = (uintptr_t)mutex1; | ||
| 129 | uintptr_t mu2int = (uintptr_t)mutex2; | ||
| 130 | if (mu1int < mu2int) { | ||
| 131 | return -1; | ||
| 132 | } else if (mu1int == mu2int) { | ||
| 133 | return 0; | ||
| 134 | } else { | ||
| 135 | return 1; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | bool | ||
| 140 | malloc_mutex_init(malloc_mutex_t *mutex, const char *name, | ||
| 141 | witness_rank_t rank, malloc_mutex_lock_order_t lock_order) { | ||
| 142 | mutex_prof_data_init(&mutex->prof_data); | ||
| 143 | #ifdef _WIN32 | ||
| 144 | # if _WIN32_WINNT >= 0x0600 | ||
| 145 | InitializeSRWLock(&mutex->lock); | ||
| 146 | # else | ||
| 147 | if (!InitializeCriticalSectionAndSpinCount(&mutex->lock, | ||
| 148 | _CRT_SPINCOUNT)) { | ||
| 149 | return true; | ||
| 150 | } | ||
| 151 | # endif | ||
| 152 | #elif (defined(JEMALLOC_OS_UNFAIR_LOCK)) | ||
| 153 | mutex->lock = OS_UNFAIR_LOCK_INIT; | ||
| 154 | #elif (defined(JEMALLOC_MUTEX_INIT_CB)) | ||
| 155 | if (postpone_init) { | ||
| 156 | mutex->postponed_next = postponed_mutexes; | ||
| 157 | postponed_mutexes = mutex; | ||
| 158 | } else { | ||
| 159 | if (_pthread_mutex_init_calloc_cb(&mutex->lock, | ||
| 160 | bootstrap_calloc) != 0) { | ||
| 161 | return true; | ||
| 162 | } | ||
| 163 | } | ||
| 164 | #else | ||
| 165 | pthread_mutexattr_t attr; | ||
| 166 | |||
| 167 | if (pthread_mutexattr_init(&attr) != 0) { | ||
| 168 | return true; | ||
| 169 | } | ||
| 170 | pthread_mutexattr_settype(&attr, MALLOC_MUTEX_TYPE); | ||
| 171 | if (pthread_mutex_init(&mutex->lock, &attr) != 0) { | ||
| 172 | pthread_mutexattr_destroy(&attr); | ||
| 173 | return true; | ||
| 174 | } | ||
| 175 | pthread_mutexattr_destroy(&attr); | ||
| 176 | #endif | ||
| 177 | if (config_debug) { | ||
| 178 | mutex->lock_order = lock_order; | ||
| 179 | if (lock_order == malloc_mutex_address_ordered) { | ||
| 180 | witness_init(&mutex->witness, name, rank, | ||
| 181 | mutex_addr_comp, mutex); | ||
| 182 | } else { | ||
| 183 | witness_init(&mutex->witness, name, rank, NULL, NULL); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | return false; | ||
| 187 | } | ||
| 188 | |||
| 189 | void | ||
| 190 | malloc_mutex_prefork(tsdn_t *tsdn, malloc_mutex_t *mutex) { | ||
| 191 | malloc_mutex_lock(tsdn, mutex); | ||
| 192 | } | ||
| 193 | |||
| 194 | void | ||
| 195 | malloc_mutex_postfork_parent(tsdn_t *tsdn, malloc_mutex_t *mutex) { | ||
| 196 | malloc_mutex_unlock(tsdn, mutex); | ||
| 197 | } | ||
| 198 | |||
| 199 | void | ||
| 200 | malloc_mutex_postfork_child(tsdn_t *tsdn, malloc_mutex_t *mutex) { | ||
| 201 | #ifdef JEMALLOC_MUTEX_INIT_CB | ||
| 202 | malloc_mutex_unlock(tsdn, mutex); | ||
| 203 | #else | ||
| 204 | if (malloc_mutex_init(mutex, mutex->witness.name, | ||
| 205 | mutex->witness.rank, mutex->lock_order)) { | ||
| 206 | malloc_printf("<jemalloc>: Error re-initializing mutex in " | ||
| 207 | "child\n"); | ||
| 208 | if (opt_abort) { | ||
| 209 | abort(); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | #endif | ||
| 213 | } | ||
| 214 | |||
| 215 | bool | ||
| 216 | malloc_mutex_boot(void) { | ||
| 217 | #ifdef JEMALLOC_MUTEX_INIT_CB | ||
| 218 | postpone_init = false; | ||
| 219 | while (postponed_mutexes != NULL) { | ||
| 220 | if (_pthread_mutex_init_calloc_cb(&postponed_mutexes->lock, | ||
| 221 | bootstrap_calloc) != 0) { | ||
| 222 | return true; | ||
| 223 | } | ||
| 224 | postponed_mutexes = postponed_mutexes->postponed_next; | ||
| 225 | } | ||
| 226 | #endif | ||
| 227 | return false; | ||
| 228 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/nstime.c b/examples/redis-unstable/deps/jemalloc/src/nstime.c deleted file mode 100644 index a1a5377..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/nstime.c +++ /dev/null | |||
| @@ -1,289 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/nstime.h" | ||
| 5 | |||
| 6 | #include "jemalloc/internal/assert.h" | ||
| 7 | |||
| 8 | #define BILLION UINT64_C(1000000000) | ||
| 9 | #define MILLION UINT64_C(1000000) | ||
| 10 | |||
| 11 | static void | ||
| 12 | nstime_set_initialized(nstime_t *time) { | ||
| 13 | #ifdef JEMALLOC_DEBUG | ||
| 14 | time->magic = NSTIME_MAGIC; | ||
| 15 | #endif | ||
| 16 | } | ||
| 17 | |||
| 18 | static void | ||
| 19 | nstime_assert_initialized(const nstime_t *time) { | ||
| 20 | #ifdef JEMALLOC_DEBUG | ||
| 21 | /* | ||
| 22 | * Some parts (e.g. stats) rely on memset to zero initialize. Treat | ||
| 23 | * these as valid initialization. | ||
| 24 | */ | ||
| 25 | assert(time->magic == NSTIME_MAGIC || | ||
| 26 | (time->magic == 0 && time->ns == 0)); | ||
| 27 | #endif | ||
| 28 | } | ||
| 29 | |||
| 30 | static void | ||
| 31 | nstime_pair_assert_initialized(const nstime_t *t1, const nstime_t *t2) { | ||
| 32 | nstime_assert_initialized(t1); | ||
| 33 | nstime_assert_initialized(t2); | ||
| 34 | } | ||
| 35 | |||
| 36 | static void | ||
| 37 | nstime_initialize_operand(nstime_t *time) { | ||
| 38 | /* | ||
| 39 | * Operations like nstime_add may have the initial operand being zero | ||
| 40 | * initialized (covered by the assert below). Full-initialize needed | ||
| 41 | * before changing it to non-zero. | ||
| 42 | */ | ||
| 43 | nstime_assert_initialized(time); | ||
| 44 | nstime_set_initialized(time); | ||
| 45 | } | ||
| 46 | |||
| 47 | void | ||
| 48 | nstime_init(nstime_t *time, uint64_t ns) { | ||
| 49 | nstime_set_initialized(time); | ||
| 50 | time->ns = ns; | ||
| 51 | } | ||
| 52 | |||
| 53 | void | ||
| 54 | nstime_init2(nstime_t *time, uint64_t sec, uint64_t nsec) { | ||
| 55 | nstime_set_initialized(time); | ||
| 56 | time->ns = sec * BILLION + nsec; | ||
| 57 | } | ||
| 58 | |||
| 59 | uint64_t | ||
| 60 | nstime_ns(const nstime_t *time) { | ||
| 61 | nstime_assert_initialized(time); | ||
| 62 | return time->ns; | ||
| 63 | } | ||
| 64 | |||
| 65 | uint64_t | ||
| 66 | nstime_msec(const nstime_t *time) { | ||
| 67 | nstime_assert_initialized(time); | ||
| 68 | return time->ns / MILLION; | ||
| 69 | } | ||
| 70 | |||
| 71 | uint64_t | ||
| 72 | nstime_sec(const nstime_t *time) { | ||
| 73 | nstime_assert_initialized(time); | ||
| 74 | return time->ns / BILLION; | ||
| 75 | } | ||
| 76 | |||
| 77 | uint64_t | ||
| 78 | nstime_nsec(const nstime_t *time) { | ||
| 79 | nstime_assert_initialized(time); | ||
| 80 | return time->ns % BILLION; | ||
| 81 | } | ||
| 82 | |||
| 83 | void | ||
| 84 | nstime_copy(nstime_t *time, const nstime_t *source) { | ||
| 85 | /* Source is required to be initialized. */ | ||
| 86 | nstime_assert_initialized(source); | ||
| 87 | *time = *source; | ||
| 88 | nstime_assert_initialized(time); | ||
| 89 | } | ||
| 90 | |||
| 91 | int | ||
| 92 | nstime_compare(const nstime_t *a, const nstime_t *b) { | ||
| 93 | nstime_pair_assert_initialized(a, b); | ||
| 94 | return (a->ns > b->ns) - (a->ns < b->ns); | ||
| 95 | } | ||
| 96 | |||
| 97 | void | ||
| 98 | nstime_add(nstime_t *time, const nstime_t *addend) { | ||
| 99 | nstime_pair_assert_initialized(time, addend); | ||
| 100 | assert(UINT64_MAX - time->ns >= addend->ns); | ||
| 101 | |||
| 102 | nstime_initialize_operand(time); | ||
| 103 | time->ns += addend->ns; | ||
| 104 | } | ||
| 105 | |||
| 106 | void | ||
| 107 | nstime_iadd(nstime_t *time, uint64_t addend) { | ||
| 108 | nstime_assert_initialized(time); | ||
| 109 | assert(UINT64_MAX - time->ns >= addend); | ||
| 110 | |||
| 111 | nstime_initialize_operand(time); | ||
| 112 | time->ns += addend; | ||
| 113 | } | ||
| 114 | |||
| 115 | void | ||
| 116 | nstime_subtract(nstime_t *time, const nstime_t *subtrahend) { | ||
| 117 | nstime_pair_assert_initialized(time, subtrahend); | ||
| 118 | assert(nstime_compare(time, subtrahend) >= 0); | ||
| 119 | |||
| 120 | /* No initialize operand -- subtraction must be initialized. */ | ||
| 121 | time->ns -= subtrahend->ns; | ||
| 122 | } | ||
| 123 | |||
| 124 | void | ||
| 125 | nstime_isubtract(nstime_t *time, uint64_t subtrahend) { | ||
| 126 | nstime_assert_initialized(time); | ||
| 127 | assert(time->ns >= subtrahend); | ||
| 128 | |||
| 129 | /* No initialize operand -- subtraction must be initialized. */ | ||
| 130 | time->ns -= subtrahend; | ||
| 131 | } | ||
| 132 | |||
| 133 | void | ||
| 134 | nstime_imultiply(nstime_t *time, uint64_t multiplier) { | ||
| 135 | nstime_assert_initialized(time); | ||
| 136 | assert((((time->ns | multiplier) & (UINT64_MAX << (sizeof(uint64_t) << | ||
| 137 | 2))) == 0) || ((time->ns * multiplier) / multiplier == time->ns)); | ||
| 138 | |||
| 139 | nstime_initialize_operand(time); | ||
| 140 | time->ns *= multiplier; | ||
| 141 | } | ||
| 142 | |||
| 143 | void | ||
| 144 | nstime_idivide(nstime_t *time, uint64_t divisor) { | ||
| 145 | nstime_assert_initialized(time); | ||
| 146 | assert(divisor != 0); | ||
| 147 | |||
| 148 | nstime_initialize_operand(time); | ||
| 149 | time->ns /= divisor; | ||
| 150 | } | ||
| 151 | |||
| 152 | uint64_t | ||
| 153 | nstime_divide(const nstime_t *time, const nstime_t *divisor) { | ||
| 154 | nstime_pair_assert_initialized(time, divisor); | ||
| 155 | assert(divisor->ns != 0); | ||
| 156 | |||
| 157 | /* No initialize operand -- *time itself remains unchanged. */ | ||
| 158 | return time->ns / divisor->ns; | ||
| 159 | } | ||
| 160 | |||
| 161 | /* Returns time since *past, w/o updating *past. */ | ||
| 162 | uint64_t | ||
| 163 | nstime_ns_since(const nstime_t *past) { | ||
| 164 | nstime_assert_initialized(past); | ||
| 165 | |||
| 166 | nstime_t now; | ||
| 167 | nstime_copy(&now, past); | ||
| 168 | nstime_update(&now); | ||
| 169 | |||
| 170 | assert(nstime_compare(&now, past) >= 0); | ||
| 171 | return now.ns - past->ns; | ||
| 172 | } | ||
| 173 | |||
| 174 | #ifdef _WIN32 | ||
| 175 | # define NSTIME_MONOTONIC true | ||
| 176 | static void | ||
| 177 | nstime_get(nstime_t *time) { | ||
| 178 | FILETIME ft; | ||
| 179 | uint64_t ticks_100ns; | ||
| 180 | |||
| 181 | GetSystemTimeAsFileTime(&ft); | ||
| 182 | ticks_100ns = (((uint64_t)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; | ||
| 183 | |||
| 184 | nstime_init(time, ticks_100ns * 100); | ||
| 185 | } | ||
| 186 | #elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE) | ||
| 187 | # define NSTIME_MONOTONIC true | ||
| 188 | static void | ||
| 189 | nstime_get(nstime_t *time) { | ||
| 190 | struct timespec ts; | ||
| 191 | |||
| 192 | clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); | ||
| 193 | nstime_init2(time, ts.tv_sec, ts.tv_nsec); | ||
| 194 | } | ||
| 195 | #elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC) | ||
| 196 | # define NSTIME_MONOTONIC true | ||
| 197 | static void | ||
| 198 | nstime_get(nstime_t *time) { | ||
| 199 | struct timespec ts; | ||
| 200 | |||
| 201 | clock_gettime(CLOCK_MONOTONIC, &ts); | ||
| 202 | nstime_init2(time, ts.tv_sec, ts.tv_nsec); | ||
| 203 | } | ||
| 204 | #elif defined(JEMALLOC_HAVE_MACH_ABSOLUTE_TIME) | ||
| 205 | # define NSTIME_MONOTONIC true | ||
| 206 | static void | ||
| 207 | nstime_get(nstime_t *time) { | ||
| 208 | nstime_init(time, mach_absolute_time()); | ||
| 209 | } | ||
| 210 | #else | ||
| 211 | # define NSTIME_MONOTONIC false | ||
| 212 | static void | ||
| 213 | nstime_get(nstime_t *time) { | ||
| 214 | struct timeval tv; | ||
| 215 | |||
| 216 | gettimeofday(&tv, NULL); | ||
| 217 | nstime_init2(time, tv.tv_sec, tv.tv_usec * 1000); | ||
| 218 | } | ||
| 219 | #endif | ||
| 220 | |||
| 221 | static bool | ||
| 222 | nstime_monotonic_impl(void) { | ||
| 223 | return NSTIME_MONOTONIC; | ||
| 224 | #undef NSTIME_MONOTONIC | ||
| 225 | } | ||
| 226 | nstime_monotonic_t *JET_MUTABLE nstime_monotonic = nstime_monotonic_impl; | ||
| 227 | |||
| 228 | prof_time_res_t opt_prof_time_res = | ||
| 229 | prof_time_res_default; | ||
| 230 | |||
| 231 | const char *prof_time_res_mode_names[] = { | ||
| 232 | "default", | ||
| 233 | "high", | ||
| 234 | }; | ||
| 235 | |||
| 236 | |||
| 237 | static void | ||
| 238 | nstime_get_realtime(nstime_t *time) { | ||
| 239 | #if defined(JEMALLOC_HAVE_CLOCK_REALTIME) && !defined(_WIN32) | ||
| 240 | struct timespec ts; | ||
| 241 | |||
| 242 | clock_gettime(CLOCK_REALTIME, &ts); | ||
| 243 | nstime_init2(time, ts.tv_sec, ts.tv_nsec); | ||
| 244 | #else | ||
| 245 | unreachable(); | ||
| 246 | #endif | ||
| 247 | } | ||
| 248 | |||
| 249 | static void | ||
| 250 | nstime_prof_update_impl(nstime_t *time) { | ||
| 251 | nstime_t old_time; | ||
| 252 | |||
| 253 | nstime_copy(&old_time, time); | ||
| 254 | |||
| 255 | if (opt_prof_time_res == prof_time_res_high) { | ||
| 256 | nstime_get_realtime(time); | ||
| 257 | } else { | ||
| 258 | nstime_get(time); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | nstime_prof_update_t *JET_MUTABLE nstime_prof_update = nstime_prof_update_impl; | ||
| 262 | |||
| 263 | static void | ||
| 264 | nstime_update_impl(nstime_t *time) { | ||
| 265 | nstime_t old_time; | ||
| 266 | |||
| 267 | nstime_copy(&old_time, time); | ||
| 268 | nstime_get(time); | ||
| 269 | |||
| 270 | /* Handle non-monotonic clocks. */ | ||
| 271 | if (unlikely(nstime_compare(&old_time, time) > 0)) { | ||
| 272 | nstime_copy(time, &old_time); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | nstime_update_t *JET_MUTABLE nstime_update = nstime_update_impl; | ||
| 276 | |||
| 277 | void | ||
| 278 | nstime_init_update(nstime_t *time) { | ||
| 279 | nstime_init_zero(time); | ||
| 280 | nstime_update(time); | ||
| 281 | } | ||
| 282 | |||
| 283 | void | ||
| 284 | nstime_prof_init_update(nstime_t *time) { | ||
| 285 | nstime_init_zero(time); | ||
| 286 | nstime_prof_update(time); | ||
| 287 | } | ||
| 288 | |||
| 289 | |||
diff --git a/examples/redis-unstable/deps/jemalloc/src/pa.c b/examples/redis-unstable/deps/jemalloc/src/pa.c deleted file mode 100644 index eb7e462..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/pa.c +++ /dev/null | |||
| @@ -1,277 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/san.h" | ||
| 5 | #include "jemalloc/internal/hpa.h" | ||
| 6 | |||
| 7 | static void | ||
| 8 | pa_nactive_add(pa_shard_t *shard, size_t add_pages) { | ||
| 9 | atomic_fetch_add_zu(&shard->nactive, add_pages, ATOMIC_RELAXED); | ||
| 10 | } | ||
| 11 | |||
| 12 | static void | ||
| 13 | pa_nactive_sub(pa_shard_t *shard, size_t sub_pages) { | ||
| 14 | assert(atomic_load_zu(&shard->nactive, ATOMIC_RELAXED) >= sub_pages); | ||
| 15 | atomic_fetch_sub_zu(&shard->nactive, sub_pages, ATOMIC_RELAXED); | ||
| 16 | } | ||
| 17 | |||
| 18 | bool | ||
| 19 | pa_central_init(pa_central_t *central, base_t *base, bool hpa, | ||
| 20 | hpa_hooks_t *hpa_hooks) { | ||
| 21 | bool err; | ||
| 22 | if (hpa) { | ||
| 23 | err = hpa_central_init(¢ral->hpa, base, hpa_hooks); | ||
| 24 | if (err) { | ||
| 25 | return true; | ||
| 26 | } | ||
| 27 | } | ||
| 28 | return false; | ||
| 29 | } | ||
| 30 | |||
| 31 | bool | ||
| 32 | pa_shard_init(tsdn_t *tsdn, pa_shard_t *shard, pa_central_t *central, | ||
| 33 | emap_t *emap, base_t *base, unsigned ind, pa_shard_stats_t *stats, | ||
| 34 | malloc_mutex_t *stats_mtx, nstime_t *cur_time, | ||
| 35 | size_t pac_oversize_threshold, ssize_t dirty_decay_ms, | ||
| 36 | ssize_t muzzy_decay_ms) { | ||
| 37 | /* This will change eventually, but for now it should hold. */ | ||
| 38 | assert(base_ind_get(base) == ind); | ||
| 39 | if (edata_cache_init(&shard->edata_cache, base)) { | ||
| 40 | return true; | ||
| 41 | } | ||
| 42 | |||
| 43 | if (pac_init(tsdn, &shard->pac, base, emap, &shard->edata_cache, | ||
| 44 | cur_time, pac_oversize_threshold, dirty_decay_ms, muzzy_decay_ms, | ||
| 45 | &stats->pac_stats, stats_mtx)) { | ||
| 46 | return true; | ||
| 47 | } | ||
| 48 | |||
| 49 | shard->ind = ind; | ||
| 50 | |||
| 51 | shard->ever_used_hpa = false; | ||
| 52 | atomic_store_b(&shard->use_hpa, false, ATOMIC_RELAXED); | ||
| 53 | |||
| 54 | atomic_store_zu(&shard->nactive, 0, ATOMIC_RELAXED); | ||
| 55 | |||
| 56 | shard->stats_mtx = stats_mtx; | ||
| 57 | shard->stats = stats; | ||
| 58 | memset(shard->stats, 0, sizeof(*shard->stats)); | ||
| 59 | |||
| 60 | shard->central = central; | ||
| 61 | shard->emap = emap; | ||
| 62 | shard->base = base; | ||
| 63 | |||
| 64 | return false; | ||
| 65 | } | ||
| 66 | |||
| 67 | bool | ||
| 68 | pa_shard_enable_hpa(tsdn_t *tsdn, pa_shard_t *shard, | ||
| 69 | const hpa_shard_opts_t *hpa_opts, const sec_opts_t *hpa_sec_opts) { | ||
| 70 | if (hpa_shard_init(&shard->hpa_shard, &shard->central->hpa, shard->emap, | ||
| 71 | shard->base, &shard->edata_cache, shard->ind, hpa_opts)) { | ||
| 72 | return true; | ||
| 73 | } | ||
| 74 | if (sec_init(tsdn, &shard->hpa_sec, shard->base, &shard->hpa_shard.pai, | ||
| 75 | hpa_sec_opts)) { | ||
| 76 | return true; | ||
| 77 | } | ||
| 78 | shard->ever_used_hpa = true; | ||
| 79 | atomic_store_b(&shard->use_hpa, true, ATOMIC_RELAXED); | ||
| 80 | |||
| 81 | return false; | ||
| 82 | } | ||
| 83 | |||
| 84 | void | ||
| 85 | pa_shard_disable_hpa(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 86 | atomic_store_b(&shard->use_hpa, false, ATOMIC_RELAXED); | ||
| 87 | if (shard->ever_used_hpa) { | ||
| 88 | sec_disable(tsdn, &shard->hpa_sec); | ||
| 89 | hpa_shard_disable(tsdn, &shard->hpa_shard); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | void | ||
| 94 | pa_shard_reset(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 95 | atomic_store_zu(&shard->nactive, 0, ATOMIC_RELAXED); | ||
| 96 | if (shard->ever_used_hpa) { | ||
| 97 | sec_flush(tsdn, &shard->hpa_sec); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | static bool | ||
| 102 | pa_shard_uses_hpa(pa_shard_t *shard) { | ||
| 103 | return atomic_load_b(&shard->use_hpa, ATOMIC_RELAXED); | ||
| 104 | } | ||
| 105 | |||
| 106 | void | ||
| 107 | pa_shard_destroy(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 108 | pac_destroy(tsdn, &shard->pac); | ||
| 109 | if (shard->ever_used_hpa) { | ||
| 110 | sec_flush(tsdn, &shard->hpa_sec); | ||
| 111 | hpa_shard_disable(tsdn, &shard->hpa_shard); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | static pai_t * | ||
| 116 | pa_get_pai(pa_shard_t *shard, edata_t *edata) { | ||
| 117 | return (edata_pai_get(edata) == EXTENT_PAI_PAC | ||
| 118 | ? &shard->pac.pai : &shard->hpa_sec.pai); | ||
| 119 | } | ||
| 120 | |||
| 121 | edata_t * | ||
| 122 | pa_alloc(tsdn_t *tsdn, pa_shard_t *shard, size_t size, size_t alignment, | ||
| 123 | bool slab, szind_t szind, bool zero, bool guarded, | ||
| 124 | bool *deferred_work_generated) { | ||
| 125 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 126 | WITNESS_RANK_CORE, 0); | ||
| 127 | assert(!guarded || alignment <= PAGE); | ||
| 128 | |||
| 129 | edata_t *edata = NULL; | ||
| 130 | if (!guarded && pa_shard_uses_hpa(shard)) { | ||
| 131 | edata = pai_alloc(tsdn, &shard->hpa_sec.pai, size, alignment, | ||
| 132 | zero, /* guarded */ false, slab, deferred_work_generated); | ||
| 133 | } | ||
| 134 | /* | ||
| 135 | * Fall back to the PAC if the HPA is off or couldn't serve the given | ||
| 136 | * allocation request. | ||
| 137 | */ | ||
| 138 | if (edata == NULL) { | ||
| 139 | edata = pai_alloc(tsdn, &shard->pac.pai, size, alignment, zero, | ||
| 140 | guarded, slab, deferred_work_generated); | ||
| 141 | } | ||
| 142 | if (edata != NULL) { | ||
| 143 | assert(edata_size_get(edata) == size); | ||
| 144 | pa_nactive_add(shard, size >> LG_PAGE); | ||
| 145 | emap_remap(tsdn, shard->emap, edata, szind, slab); | ||
| 146 | edata_szind_set(edata, szind); | ||
| 147 | edata_slab_set(edata, slab); | ||
| 148 | if (slab && (size > 2 * PAGE)) { | ||
| 149 | emap_register_interior(tsdn, shard->emap, edata, szind); | ||
| 150 | } | ||
| 151 | assert(edata_arena_ind_get(edata) == shard->ind); | ||
| 152 | } | ||
| 153 | return edata; | ||
| 154 | } | ||
| 155 | |||
| 156 | bool | ||
| 157 | pa_expand(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, size_t old_size, | ||
| 158 | size_t new_size, szind_t szind, bool zero, bool *deferred_work_generated) { | ||
| 159 | assert(new_size > old_size); | ||
| 160 | assert(edata_size_get(edata) == old_size); | ||
| 161 | assert((new_size & PAGE_MASK) == 0); | ||
| 162 | if (edata_guarded_get(edata)) { | ||
| 163 | return true; | ||
| 164 | } | ||
| 165 | size_t expand_amount = new_size - old_size; | ||
| 166 | |||
| 167 | pai_t *pai = pa_get_pai(shard, edata); | ||
| 168 | |||
| 169 | bool error = pai_expand(tsdn, pai, edata, old_size, new_size, zero, | ||
| 170 | deferred_work_generated); | ||
| 171 | if (error) { | ||
| 172 | return true; | ||
| 173 | } | ||
| 174 | |||
| 175 | pa_nactive_add(shard, expand_amount >> LG_PAGE); | ||
| 176 | edata_szind_set(edata, szind); | ||
| 177 | emap_remap(tsdn, shard->emap, edata, szind, /* slab */ false); | ||
| 178 | return false; | ||
| 179 | } | ||
| 180 | |||
| 181 | bool | ||
| 182 | pa_shrink(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, size_t old_size, | ||
| 183 | size_t new_size, szind_t szind, bool *deferred_work_generated) { | ||
| 184 | assert(new_size < old_size); | ||
| 185 | assert(edata_size_get(edata) == old_size); | ||
| 186 | assert((new_size & PAGE_MASK) == 0); | ||
| 187 | if (edata_guarded_get(edata)) { | ||
| 188 | return true; | ||
| 189 | } | ||
| 190 | size_t shrink_amount = old_size - new_size; | ||
| 191 | |||
| 192 | pai_t *pai = pa_get_pai(shard, edata); | ||
| 193 | bool error = pai_shrink(tsdn, pai, edata, old_size, new_size, | ||
| 194 | deferred_work_generated); | ||
| 195 | if (error) { | ||
| 196 | return true; | ||
| 197 | } | ||
| 198 | pa_nactive_sub(shard, shrink_amount >> LG_PAGE); | ||
| 199 | |||
| 200 | edata_szind_set(edata, szind); | ||
| 201 | emap_remap(tsdn, shard->emap, edata, szind, /* slab */ false); | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | void | ||
| 206 | pa_dalloc(tsdn_t *tsdn, pa_shard_t *shard, edata_t *edata, | ||
| 207 | bool *deferred_work_generated) { | ||
| 208 | emap_remap(tsdn, shard->emap, edata, SC_NSIZES, /* slab */ false); | ||
| 209 | if (edata_slab_get(edata)) { | ||
| 210 | emap_deregister_interior(tsdn, shard->emap, edata); | ||
| 211 | /* | ||
| 212 | * The slab state of the extent isn't cleared. It may be used | ||
| 213 | * by the pai implementation, e.g. to make caching decisions. | ||
| 214 | */ | ||
| 215 | } | ||
| 216 | edata_addr_set(edata, edata_base_get(edata)); | ||
| 217 | edata_szind_set(edata, SC_NSIZES); | ||
| 218 | pa_nactive_sub(shard, edata_size_get(edata) >> LG_PAGE); | ||
| 219 | pai_t *pai = pa_get_pai(shard, edata); | ||
| 220 | pai_dalloc(tsdn, pai, edata, deferred_work_generated); | ||
| 221 | } | ||
| 222 | |||
| 223 | bool | ||
| 224 | pa_shard_retain_grow_limit_get_set(tsdn_t *tsdn, pa_shard_t *shard, | ||
| 225 | size_t *old_limit, size_t *new_limit) { | ||
| 226 | return pac_retain_grow_limit_get_set(tsdn, &shard->pac, old_limit, | ||
| 227 | new_limit); | ||
| 228 | } | ||
| 229 | |||
| 230 | bool | ||
| 231 | pa_decay_ms_set(tsdn_t *tsdn, pa_shard_t *shard, extent_state_t state, | ||
| 232 | ssize_t decay_ms, pac_purge_eagerness_t eagerness) { | ||
| 233 | return pac_decay_ms_set(tsdn, &shard->pac, state, decay_ms, eagerness); | ||
| 234 | } | ||
| 235 | |||
| 236 | ssize_t | ||
| 237 | pa_decay_ms_get(pa_shard_t *shard, extent_state_t state) { | ||
| 238 | return pac_decay_ms_get(&shard->pac, state); | ||
| 239 | } | ||
| 240 | |||
| 241 | void | ||
| 242 | pa_shard_set_deferral_allowed(tsdn_t *tsdn, pa_shard_t *shard, | ||
| 243 | bool deferral_allowed) { | ||
| 244 | if (pa_shard_uses_hpa(shard)) { | ||
| 245 | hpa_shard_set_deferral_allowed(tsdn, &shard->hpa_shard, | ||
| 246 | deferral_allowed); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | void | ||
| 251 | pa_shard_do_deferred_work(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 252 | if (pa_shard_uses_hpa(shard)) { | ||
| 253 | hpa_shard_do_deferred_work(tsdn, &shard->hpa_shard); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | /* | ||
| 258 | * Get time until next deferred work ought to happen. If there are multiple | ||
| 259 | * things that have been deferred, this function calculates the time until | ||
| 260 | * the soonest of those things. | ||
| 261 | */ | ||
| 262 | uint64_t | ||
| 263 | pa_shard_time_until_deferred_work(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 264 | uint64_t time = pai_time_until_deferred_work(tsdn, &shard->pac.pai); | ||
| 265 | if (time == BACKGROUND_THREAD_DEFERRED_MIN) { | ||
| 266 | return time; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (pa_shard_uses_hpa(shard)) { | ||
| 270 | uint64_t hpa = | ||
| 271 | pai_time_until_deferred_work(tsdn, &shard->hpa_shard.pai); | ||
| 272 | if (hpa < time) { | ||
| 273 | time = hpa; | ||
| 274 | } | ||
| 275 | } | ||
| 276 | return time; | ||
| 277 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/pa_extra.c b/examples/redis-unstable/deps/jemalloc/src/pa_extra.c deleted file mode 100644 index 0f488be..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/pa_extra.c +++ /dev/null | |||
| @@ -1,191 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | /* | ||
| 5 | * This file is logically part of the PA module. While pa.c contains the core | ||
| 6 | * allocator functionality, this file contains boring integration functionality; | ||
| 7 | * things like the pre- and post- fork handlers, and stats merging for CTL | ||
| 8 | * refreshes. | ||
| 9 | */ | ||
| 10 | |||
| 11 | void | ||
| 12 | pa_shard_prefork0(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 13 | malloc_mutex_prefork(tsdn, &shard->pac.decay_dirty.mtx); | ||
| 14 | malloc_mutex_prefork(tsdn, &shard->pac.decay_muzzy.mtx); | ||
| 15 | } | ||
| 16 | |||
| 17 | void | ||
| 18 | pa_shard_prefork2(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 19 | if (shard->ever_used_hpa) { | ||
| 20 | sec_prefork2(tsdn, &shard->hpa_sec); | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | void | ||
| 25 | pa_shard_prefork3(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 26 | malloc_mutex_prefork(tsdn, &shard->pac.grow_mtx); | ||
| 27 | if (shard->ever_used_hpa) { | ||
| 28 | hpa_shard_prefork3(tsdn, &shard->hpa_shard); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | void | ||
| 33 | pa_shard_prefork4(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 34 | ecache_prefork(tsdn, &shard->pac.ecache_dirty); | ||
| 35 | ecache_prefork(tsdn, &shard->pac.ecache_muzzy); | ||
| 36 | ecache_prefork(tsdn, &shard->pac.ecache_retained); | ||
| 37 | if (shard->ever_used_hpa) { | ||
| 38 | hpa_shard_prefork4(tsdn, &shard->hpa_shard); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | void | ||
| 43 | pa_shard_prefork5(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 44 | edata_cache_prefork(tsdn, &shard->edata_cache); | ||
| 45 | } | ||
| 46 | |||
| 47 | void | ||
| 48 | pa_shard_postfork_parent(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 49 | edata_cache_postfork_parent(tsdn, &shard->edata_cache); | ||
| 50 | ecache_postfork_parent(tsdn, &shard->pac.ecache_dirty); | ||
| 51 | ecache_postfork_parent(tsdn, &shard->pac.ecache_muzzy); | ||
| 52 | ecache_postfork_parent(tsdn, &shard->pac.ecache_retained); | ||
| 53 | malloc_mutex_postfork_parent(tsdn, &shard->pac.grow_mtx); | ||
| 54 | malloc_mutex_postfork_parent(tsdn, &shard->pac.decay_dirty.mtx); | ||
| 55 | malloc_mutex_postfork_parent(tsdn, &shard->pac.decay_muzzy.mtx); | ||
| 56 | if (shard->ever_used_hpa) { | ||
| 57 | sec_postfork_parent(tsdn, &shard->hpa_sec); | ||
| 58 | hpa_shard_postfork_parent(tsdn, &shard->hpa_shard); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | void | ||
| 63 | pa_shard_postfork_child(tsdn_t *tsdn, pa_shard_t *shard) { | ||
| 64 | edata_cache_postfork_child(tsdn, &shard->edata_cache); | ||
| 65 | ecache_postfork_child(tsdn, &shard->pac.ecache_dirty); | ||
| 66 | ecache_postfork_child(tsdn, &shard->pac.ecache_muzzy); | ||
| 67 | ecache_postfork_child(tsdn, &shard->pac.ecache_retained); | ||
| 68 | malloc_mutex_postfork_child(tsdn, &shard->pac.grow_mtx); | ||
| 69 | malloc_mutex_postfork_child(tsdn, &shard->pac.decay_dirty.mtx); | ||
| 70 | malloc_mutex_postfork_child(tsdn, &shard->pac.decay_muzzy.mtx); | ||
| 71 | if (shard->ever_used_hpa) { | ||
| 72 | sec_postfork_child(tsdn, &shard->hpa_sec); | ||
| 73 | hpa_shard_postfork_child(tsdn, &shard->hpa_shard); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | void | ||
| 78 | pa_shard_basic_stats_merge(pa_shard_t *shard, size_t *nactive, size_t *ndirty, | ||
| 79 | size_t *nmuzzy) { | ||
| 80 | *nactive += atomic_load_zu(&shard->nactive, ATOMIC_RELAXED); | ||
| 81 | *ndirty += ecache_npages_get(&shard->pac.ecache_dirty); | ||
| 82 | *nmuzzy += ecache_npages_get(&shard->pac.ecache_muzzy); | ||
| 83 | } | ||
| 84 | |||
| 85 | void | ||
| 86 | pa_shard_stats_merge(tsdn_t *tsdn, pa_shard_t *shard, | ||
| 87 | pa_shard_stats_t *pa_shard_stats_out, pac_estats_t *estats_out, | ||
| 88 | hpa_shard_stats_t *hpa_stats_out, sec_stats_t *sec_stats_out, | ||
| 89 | size_t *resident) { | ||
| 90 | cassert(config_stats); | ||
| 91 | |||
| 92 | pa_shard_stats_out->pac_stats.retained += | ||
| 93 | ecache_npages_get(&shard->pac.ecache_retained) << LG_PAGE; | ||
| 94 | pa_shard_stats_out->edata_avail += atomic_load_zu( | ||
| 95 | &shard->edata_cache.count, ATOMIC_RELAXED); | ||
| 96 | |||
| 97 | size_t resident_pgs = 0; | ||
| 98 | resident_pgs += atomic_load_zu(&shard->nactive, ATOMIC_RELAXED); | ||
| 99 | resident_pgs += ecache_npages_get(&shard->pac.ecache_dirty); | ||
| 100 | *resident += (resident_pgs << LG_PAGE); | ||
| 101 | |||
| 102 | /* Dirty decay stats */ | ||
| 103 | locked_inc_u64_unsynchronized( | ||
| 104 | &pa_shard_stats_out->pac_stats.decay_dirty.npurge, | ||
| 105 | locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), | ||
| 106 | &shard->pac.stats->decay_dirty.npurge)); | ||
| 107 | locked_inc_u64_unsynchronized( | ||
| 108 | &pa_shard_stats_out->pac_stats.decay_dirty.nmadvise, | ||
| 109 | locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), | ||
| 110 | &shard->pac.stats->decay_dirty.nmadvise)); | ||
| 111 | locked_inc_u64_unsynchronized( | ||
| 112 | &pa_shard_stats_out->pac_stats.decay_dirty.purged, | ||
| 113 | locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), | ||
| 114 | &shard->pac.stats->decay_dirty.purged)); | ||
| 115 | |||
| 116 | /* Muzzy decay stats */ | ||
| 117 | locked_inc_u64_unsynchronized( | ||
| 118 | &pa_shard_stats_out->pac_stats.decay_muzzy.npurge, | ||
| 119 | locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), | ||
| 120 | &shard->pac.stats->decay_muzzy.npurge)); | ||
| 121 | locked_inc_u64_unsynchronized( | ||
| 122 | &pa_shard_stats_out->pac_stats.decay_muzzy.nmadvise, | ||
| 123 | locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), | ||
| 124 | &shard->pac.stats->decay_muzzy.nmadvise)); | ||
| 125 | locked_inc_u64_unsynchronized( | ||
| 126 | &pa_shard_stats_out->pac_stats.decay_muzzy.purged, | ||
| 127 | locked_read_u64(tsdn, LOCKEDINT_MTX(*shard->stats_mtx), | ||
| 128 | &shard->pac.stats->decay_muzzy.purged)); | ||
| 129 | |||
| 130 | atomic_load_add_store_zu(&pa_shard_stats_out->pac_stats.abandoned_vm, | ||
| 131 | atomic_load_zu(&shard->pac.stats->abandoned_vm, ATOMIC_RELAXED)); | ||
| 132 | |||
| 133 | for (pszind_t i = 0; i < SC_NPSIZES; i++) { | ||
| 134 | size_t dirty, muzzy, retained, dirty_bytes, muzzy_bytes, | ||
| 135 | retained_bytes; | ||
| 136 | dirty = ecache_nextents_get(&shard->pac.ecache_dirty, i); | ||
| 137 | muzzy = ecache_nextents_get(&shard->pac.ecache_muzzy, i); | ||
| 138 | retained = ecache_nextents_get(&shard->pac.ecache_retained, i); | ||
| 139 | dirty_bytes = ecache_nbytes_get(&shard->pac.ecache_dirty, i); | ||
| 140 | muzzy_bytes = ecache_nbytes_get(&shard->pac.ecache_muzzy, i); | ||
| 141 | retained_bytes = ecache_nbytes_get(&shard->pac.ecache_retained, | ||
| 142 | i); | ||
| 143 | |||
| 144 | estats_out[i].ndirty = dirty; | ||
| 145 | estats_out[i].nmuzzy = muzzy; | ||
| 146 | estats_out[i].nretained = retained; | ||
| 147 | estats_out[i].dirty_bytes = dirty_bytes; | ||
| 148 | estats_out[i].muzzy_bytes = muzzy_bytes; | ||
| 149 | estats_out[i].retained_bytes = retained_bytes; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (shard->ever_used_hpa) { | ||
| 153 | hpa_shard_stats_merge(tsdn, &shard->hpa_shard, hpa_stats_out); | ||
| 154 | sec_stats_merge(tsdn, &shard->hpa_sec, sec_stats_out); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | static void | ||
| 159 | pa_shard_mtx_stats_read_single(tsdn_t *tsdn, mutex_prof_data_t *mutex_prof_data, | ||
| 160 | malloc_mutex_t *mtx, int ind) { | ||
| 161 | malloc_mutex_lock(tsdn, mtx); | ||
| 162 | malloc_mutex_prof_read(tsdn, &mutex_prof_data[ind], mtx); | ||
| 163 | malloc_mutex_unlock(tsdn, mtx); | ||
| 164 | } | ||
| 165 | |||
| 166 | void | ||
| 167 | pa_shard_mtx_stats_read(tsdn_t *tsdn, pa_shard_t *shard, | ||
| 168 | mutex_prof_data_t mutex_prof_data[mutex_prof_num_arena_mutexes]) { | ||
| 169 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 170 | &shard->edata_cache.mtx, arena_prof_mutex_extent_avail); | ||
| 171 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 172 | &shard->pac.ecache_dirty.mtx, arena_prof_mutex_extents_dirty); | ||
| 173 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 174 | &shard->pac.ecache_muzzy.mtx, arena_prof_mutex_extents_muzzy); | ||
| 175 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 176 | &shard->pac.ecache_retained.mtx, arena_prof_mutex_extents_retained); | ||
| 177 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 178 | &shard->pac.decay_dirty.mtx, arena_prof_mutex_decay_dirty); | ||
| 179 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 180 | &shard->pac.decay_muzzy.mtx, arena_prof_mutex_decay_muzzy); | ||
| 181 | |||
| 182 | if (shard->ever_used_hpa) { | ||
| 183 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 184 | &shard->hpa_shard.mtx, arena_prof_mutex_hpa_shard); | ||
| 185 | pa_shard_mtx_stats_read_single(tsdn, mutex_prof_data, | ||
| 186 | &shard->hpa_shard.grow_mtx, | ||
| 187 | arena_prof_mutex_hpa_shard_grow); | ||
| 188 | sec_mutex_stats_read(tsdn, &shard->hpa_sec, | ||
| 189 | &mutex_prof_data[arena_prof_mutex_hpa_sec]); | ||
| 190 | } | ||
| 191 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/pac.c b/examples/redis-unstable/deps/jemalloc/src/pac.c deleted file mode 100644 index 53e3d82..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/pac.c +++ /dev/null | |||
| @@ -1,587 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/pac.h" | ||
| 5 | #include "jemalloc/internal/san.h" | ||
| 6 | |||
| 7 | static edata_t *pac_alloc_impl(tsdn_t *tsdn, pai_t *self, size_t size, | ||
| 8 | size_t alignment, bool zero, bool guarded, bool frequent_reuse, | ||
| 9 | bool *deferred_work_generated); | ||
| 10 | static bool pac_expand_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 11 | size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); | ||
| 12 | static bool pac_shrink_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 13 | size_t old_size, size_t new_size, bool *deferred_work_generated); | ||
| 14 | static void pac_dalloc_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 15 | bool *deferred_work_generated); | ||
| 16 | static uint64_t pac_time_until_deferred_work(tsdn_t *tsdn, pai_t *self); | ||
| 17 | |||
| 18 | static inline void | ||
| 19 | pac_decay_data_get(pac_t *pac, extent_state_t state, | ||
| 20 | decay_t **r_decay, pac_decay_stats_t **r_decay_stats, ecache_t **r_ecache) { | ||
| 21 | switch(state) { | ||
| 22 | case extent_state_dirty: | ||
| 23 | *r_decay = &pac->decay_dirty; | ||
| 24 | *r_decay_stats = &pac->stats->decay_dirty; | ||
| 25 | *r_ecache = &pac->ecache_dirty; | ||
| 26 | return; | ||
| 27 | case extent_state_muzzy: | ||
| 28 | *r_decay = &pac->decay_muzzy; | ||
| 29 | *r_decay_stats = &pac->stats->decay_muzzy; | ||
| 30 | *r_ecache = &pac->ecache_muzzy; | ||
| 31 | return; | ||
| 32 | default: | ||
| 33 | unreachable(); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | bool | ||
| 38 | pac_init(tsdn_t *tsdn, pac_t *pac, base_t *base, emap_t *emap, | ||
| 39 | edata_cache_t *edata_cache, nstime_t *cur_time, | ||
| 40 | size_t pac_oversize_threshold, ssize_t dirty_decay_ms, | ||
| 41 | ssize_t muzzy_decay_ms, pac_stats_t *pac_stats, malloc_mutex_t *stats_mtx) { | ||
| 42 | unsigned ind = base_ind_get(base); | ||
| 43 | /* | ||
| 44 | * Delay coalescing for dirty extents despite the disruptive effect on | ||
| 45 | * memory layout for best-fit extent allocation, since cached extents | ||
| 46 | * are likely to be reused soon after deallocation, and the cost of | ||
| 47 | * merging/splitting extents is non-trivial. | ||
| 48 | */ | ||
| 49 | if (ecache_init(tsdn, &pac->ecache_dirty, extent_state_dirty, ind, | ||
| 50 | /* delay_coalesce */ true)) { | ||
| 51 | return true; | ||
| 52 | } | ||
| 53 | /* | ||
| 54 | * Coalesce muzzy extents immediately, because operations on them are in | ||
| 55 | * the critical path much less often than for dirty extents. | ||
| 56 | */ | ||
| 57 | if (ecache_init(tsdn, &pac->ecache_muzzy, extent_state_muzzy, ind, | ||
| 58 | /* delay_coalesce */ false)) { | ||
| 59 | return true; | ||
| 60 | } | ||
| 61 | /* | ||
| 62 | * Coalesce retained extents immediately, in part because they will | ||
| 63 | * never be evicted (and therefore there's no opportunity for delayed | ||
| 64 | * coalescing), but also because operations on retained extents are not | ||
| 65 | * in the critical path. | ||
| 66 | */ | ||
| 67 | if (ecache_init(tsdn, &pac->ecache_retained, extent_state_retained, | ||
| 68 | ind, /* delay_coalesce */ false)) { | ||
| 69 | return true; | ||
| 70 | } | ||
| 71 | exp_grow_init(&pac->exp_grow); | ||
| 72 | if (malloc_mutex_init(&pac->grow_mtx, "extent_grow", | ||
| 73 | WITNESS_RANK_EXTENT_GROW, malloc_mutex_rank_exclusive)) { | ||
| 74 | return true; | ||
| 75 | } | ||
| 76 | atomic_store_zu(&pac->oversize_threshold, pac_oversize_threshold, | ||
| 77 | ATOMIC_RELAXED); | ||
| 78 | if (decay_init(&pac->decay_dirty, cur_time, dirty_decay_ms)) { | ||
| 79 | return true; | ||
| 80 | } | ||
| 81 | if (decay_init(&pac->decay_muzzy, cur_time, muzzy_decay_ms)) { | ||
| 82 | return true; | ||
| 83 | } | ||
| 84 | if (san_bump_alloc_init(&pac->sba)) { | ||
| 85 | return true; | ||
| 86 | } | ||
| 87 | |||
| 88 | pac->base = base; | ||
| 89 | pac->emap = emap; | ||
| 90 | pac->edata_cache = edata_cache; | ||
| 91 | pac->stats = pac_stats; | ||
| 92 | pac->stats_mtx = stats_mtx; | ||
| 93 | atomic_store_zu(&pac->extent_sn_next, 0, ATOMIC_RELAXED); | ||
| 94 | |||
| 95 | pac->pai.alloc = &pac_alloc_impl; | ||
| 96 | pac->pai.alloc_batch = &pai_alloc_batch_default; | ||
| 97 | pac->pai.expand = &pac_expand_impl; | ||
| 98 | pac->pai.shrink = &pac_shrink_impl; | ||
| 99 | pac->pai.dalloc = &pac_dalloc_impl; | ||
| 100 | pac->pai.dalloc_batch = &pai_dalloc_batch_default; | ||
| 101 | pac->pai.time_until_deferred_work = &pac_time_until_deferred_work; | ||
| 102 | |||
| 103 | return false; | ||
| 104 | } | ||
| 105 | |||
| 106 | static inline bool | ||
| 107 | pac_may_have_muzzy(pac_t *pac) { | ||
| 108 | return pac_decay_ms_get(pac, extent_state_muzzy) != 0; | ||
| 109 | } | ||
| 110 | |||
| 111 | static edata_t * | ||
| 112 | pac_alloc_real(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, size_t size, | ||
| 113 | size_t alignment, bool zero, bool guarded) { | ||
| 114 | assert(!guarded || alignment <= PAGE); | ||
| 115 | |||
| 116 | edata_t *edata = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_dirty, | ||
| 117 | NULL, size, alignment, zero, guarded); | ||
| 118 | |||
| 119 | if (edata == NULL && pac_may_have_muzzy(pac)) { | ||
| 120 | edata = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_muzzy, | ||
| 121 | NULL, size, alignment, zero, guarded); | ||
| 122 | } | ||
| 123 | if (edata == NULL) { | ||
| 124 | edata = ecache_alloc_grow(tsdn, pac, ehooks, | ||
| 125 | &pac->ecache_retained, NULL, size, alignment, zero, | ||
| 126 | guarded); | ||
| 127 | if (config_stats && edata != NULL) { | ||
| 128 | atomic_fetch_add_zu(&pac->stats->pac_mapped, size, | ||
| 129 | ATOMIC_RELAXED); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | return edata; | ||
| 134 | } | ||
| 135 | |||
| 136 | static edata_t * | ||
| 137 | pac_alloc_new_guarded(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, size_t size, | ||
| 138 | size_t alignment, bool zero, bool frequent_reuse) { | ||
| 139 | assert(alignment <= PAGE); | ||
| 140 | |||
| 141 | edata_t *edata; | ||
| 142 | if (san_bump_enabled() && frequent_reuse) { | ||
| 143 | edata = san_bump_alloc(tsdn, &pac->sba, pac, ehooks, size, | ||
| 144 | zero); | ||
| 145 | } else { | ||
| 146 | size_t size_with_guards = san_two_side_guarded_sz(size); | ||
| 147 | /* Alloc a non-guarded extent first.*/ | ||
| 148 | edata = pac_alloc_real(tsdn, pac, ehooks, size_with_guards, | ||
| 149 | /* alignment */ PAGE, zero, /* guarded */ false); | ||
| 150 | if (edata != NULL) { | ||
| 151 | /* Add guards around it. */ | ||
| 152 | assert(edata_size_get(edata) == size_with_guards); | ||
| 153 | san_guard_pages_two_sided(tsdn, ehooks, edata, | ||
| 154 | pac->emap, true); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | assert(edata == NULL || (edata_guarded_get(edata) && | ||
| 158 | edata_size_get(edata) == size)); | ||
| 159 | |||
| 160 | return edata; | ||
| 161 | } | ||
| 162 | |||
| 163 | static edata_t * | ||
| 164 | pac_alloc_impl(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, | ||
| 165 | bool zero, bool guarded, bool frequent_reuse, | ||
| 166 | bool *deferred_work_generated) { | ||
| 167 | pac_t *pac = (pac_t *)self; | ||
| 168 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 169 | |||
| 170 | edata_t *edata = NULL; | ||
| 171 | /* | ||
| 172 | * The condition is an optimization - not frequently reused guarded | ||
| 173 | * allocations are never put in the ecache. pac_alloc_real also | ||
| 174 | * doesn't grow retained for guarded allocations. So pac_alloc_real | ||
| 175 | * for such allocations would always return NULL. | ||
| 176 | * */ | ||
| 177 | if (!guarded || frequent_reuse) { | ||
| 178 | edata = pac_alloc_real(tsdn, pac, ehooks, size, alignment, | ||
| 179 | zero, guarded); | ||
| 180 | } | ||
| 181 | if (edata == NULL && guarded) { | ||
| 182 | /* No cached guarded extents; creating a new one. */ | ||
| 183 | edata = pac_alloc_new_guarded(tsdn, pac, ehooks, size, | ||
| 184 | alignment, zero, frequent_reuse); | ||
| 185 | } | ||
| 186 | |||
| 187 | return edata; | ||
| 188 | } | ||
| 189 | |||
| 190 | static bool | ||
| 191 | pac_expand_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, | ||
| 192 | size_t new_size, bool zero, bool *deferred_work_generated) { | ||
| 193 | pac_t *pac = (pac_t *)self; | ||
| 194 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 195 | |||
| 196 | size_t mapped_add = 0; | ||
| 197 | size_t expand_amount = new_size - old_size; | ||
| 198 | |||
| 199 | if (ehooks_merge_will_fail(ehooks)) { | ||
| 200 | return true; | ||
| 201 | } | ||
| 202 | edata_t *trail = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_dirty, | ||
| 203 | edata, expand_amount, PAGE, zero, /* guarded*/ false); | ||
| 204 | if (trail == NULL) { | ||
| 205 | trail = ecache_alloc(tsdn, pac, ehooks, &pac->ecache_muzzy, | ||
| 206 | edata, expand_amount, PAGE, zero, /* guarded*/ false); | ||
| 207 | } | ||
| 208 | if (trail == NULL) { | ||
| 209 | trail = ecache_alloc_grow(tsdn, pac, ehooks, | ||
| 210 | &pac->ecache_retained, edata, expand_amount, PAGE, zero, | ||
| 211 | /* guarded */ false); | ||
| 212 | mapped_add = expand_amount; | ||
| 213 | } | ||
| 214 | if (trail == NULL) { | ||
| 215 | return true; | ||
| 216 | } | ||
| 217 | if (extent_merge_wrapper(tsdn, pac, ehooks, edata, trail)) { | ||
| 218 | extent_dalloc_wrapper(tsdn, pac, ehooks, trail); | ||
| 219 | return true; | ||
| 220 | } | ||
| 221 | if (config_stats && mapped_add > 0) { | ||
| 222 | atomic_fetch_add_zu(&pac->stats->pac_mapped, mapped_add, | ||
| 223 | ATOMIC_RELAXED); | ||
| 224 | } | ||
| 225 | return false; | ||
| 226 | } | ||
| 227 | |||
| 228 | static bool | ||
| 229 | pac_shrink_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, | ||
| 230 | size_t new_size, bool *deferred_work_generated) { | ||
| 231 | pac_t *pac = (pac_t *)self; | ||
| 232 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 233 | |||
| 234 | size_t shrink_amount = old_size - new_size; | ||
| 235 | |||
| 236 | if (ehooks_split_will_fail(ehooks)) { | ||
| 237 | return true; | ||
| 238 | } | ||
| 239 | |||
| 240 | edata_t *trail = extent_split_wrapper(tsdn, pac, ehooks, edata, | ||
| 241 | new_size, shrink_amount, /* holding_core_locks */ false); | ||
| 242 | if (trail == NULL) { | ||
| 243 | return true; | ||
| 244 | } | ||
| 245 | ecache_dalloc(tsdn, pac, ehooks, &pac->ecache_dirty, trail); | ||
| 246 | *deferred_work_generated = true; | ||
| 247 | return false; | ||
| 248 | } | ||
| 249 | |||
| 250 | static void | ||
| 251 | pac_dalloc_impl(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 252 | bool *deferred_work_generated) { | ||
| 253 | pac_t *pac = (pac_t *)self; | ||
| 254 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 255 | |||
| 256 | if (edata_guarded_get(edata)) { | ||
| 257 | /* | ||
| 258 | * Because cached guarded extents do exact fit only, large | ||
| 259 | * guarded extents are restored on dalloc eagerly (otherwise | ||
| 260 | * they will not be reused efficiently). Slab sizes have a | ||
| 261 | * limited number of size classes, and tend to cycle faster. | ||
| 262 | * | ||
| 263 | * In the case where coalesce is restrained (VirtualFree on | ||
| 264 | * Windows), guarded extents are also not cached -- otherwise | ||
| 265 | * during arena destroy / reset, the retained extents would not | ||
| 266 | * be whole regions (i.e. they are split between regular and | ||
| 267 | * guarded). | ||
| 268 | */ | ||
| 269 | if (!edata_slab_get(edata) || !maps_coalesce) { | ||
| 270 | assert(edata_size_get(edata) >= SC_LARGE_MINCLASS || | ||
| 271 | !maps_coalesce); | ||
| 272 | san_unguard_pages_two_sided(tsdn, ehooks, edata, | ||
| 273 | pac->emap); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | ecache_dalloc(tsdn, pac, ehooks, &pac->ecache_dirty, edata); | ||
| 278 | /* Purging of deallocated pages is deferred */ | ||
| 279 | *deferred_work_generated = true; | ||
| 280 | } | ||
| 281 | |||
| 282 | static inline uint64_t | ||
| 283 | pac_ns_until_purge(tsdn_t *tsdn, decay_t *decay, size_t npages) { | ||
| 284 | if (malloc_mutex_trylock(tsdn, &decay->mtx)) { | ||
| 285 | /* Use minimal interval if decay is contended. */ | ||
| 286 | return BACKGROUND_THREAD_DEFERRED_MIN; | ||
| 287 | } | ||
| 288 | uint64_t result = decay_ns_until_purge(decay, npages, | ||
| 289 | ARENA_DEFERRED_PURGE_NPAGES_THRESHOLD); | ||
| 290 | |||
| 291 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 292 | return result; | ||
| 293 | } | ||
| 294 | |||
| 295 | static uint64_t | ||
| 296 | pac_time_until_deferred_work(tsdn_t *tsdn, pai_t *self) { | ||
| 297 | uint64_t time; | ||
| 298 | pac_t *pac = (pac_t *)self; | ||
| 299 | |||
| 300 | time = pac_ns_until_purge(tsdn, | ||
| 301 | &pac->decay_dirty, | ||
| 302 | ecache_npages_get(&pac->ecache_dirty)); | ||
| 303 | if (time == BACKGROUND_THREAD_DEFERRED_MIN) { | ||
| 304 | return time; | ||
| 305 | } | ||
| 306 | |||
| 307 | uint64_t muzzy = pac_ns_until_purge(tsdn, | ||
| 308 | &pac->decay_muzzy, | ||
| 309 | ecache_npages_get(&pac->ecache_muzzy)); | ||
| 310 | if (muzzy < time) { | ||
| 311 | time = muzzy; | ||
| 312 | } | ||
| 313 | return time; | ||
| 314 | } | ||
| 315 | |||
| 316 | bool | ||
| 317 | pac_retain_grow_limit_get_set(tsdn_t *tsdn, pac_t *pac, size_t *old_limit, | ||
| 318 | size_t *new_limit) { | ||
| 319 | pszind_t new_ind JEMALLOC_CC_SILENCE_INIT(0); | ||
| 320 | if (new_limit != NULL) { | ||
| 321 | size_t limit = *new_limit; | ||
| 322 | /* Grow no more than the new limit. */ | ||
| 323 | if ((new_ind = sz_psz2ind(limit + 1) - 1) >= SC_NPSIZES) { | ||
| 324 | return true; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | malloc_mutex_lock(tsdn, &pac->grow_mtx); | ||
| 329 | if (old_limit != NULL) { | ||
| 330 | *old_limit = sz_pind2sz(pac->exp_grow.limit); | ||
| 331 | } | ||
| 332 | if (new_limit != NULL) { | ||
| 333 | pac->exp_grow.limit = new_ind; | ||
| 334 | } | ||
| 335 | malloc_mutex_unlock(tsdn, &pac->grow_mtx); | ||
| 336 | |||
| 337 | return false; | ||
| 338 | } | ||
| 339 | |||
| 340 | static size_t | ||
| 341 | pac_stash_decayed(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, | ||
| 342 | size_t npages_limit, size_t npages_decay_max, | ||
| 343 | edata_list_inactive_t *result) { | ||
| 344 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 345 | WITNESS_RANK_CORE, 0); | ||
| 346 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 347 | |||
| 348 | /* Stash extents according to npages_limit. */ | ||
| 349 | size_t nstashed = 0; | ||
| 350 | while (nstashed < npages_decay_max) { | ||
| 351 | edata_t *edata = ecache_evict(tsdn, pac, ehooks, ecache, | ||
| 352 | npages_limit); | ||
| 353 | if (edata == NULL) { | ||
| 354 | break; | ||
| 355 | } | ||
| 356 | edata_list_inactive_append(result, edata); | ||
| 357 | nstashed += edata_size_get(edata) >> LG_PAGE; | ||
| 358 | } | ||
| 359 | return nstashed; | ||
| 360 | } | ||
| 361 | |||
| 362 | static size_t | ||
| 363 | pac_decay_stashed(tsdn_t *tsdn, pac_t *pac, decay_t *decay, | ||
| 364 | pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay, | ||
| 365 | edata_list_inactive_t *decay_extents) { | ||
| 366 | bool err; | ||
| 367 | |||
| 368 | size_t nmadvise = 0; | ||
| 369 | size_t nunmapped = 0; | ||
| 370 | size_t npurged = 0; | ||
| 371 | |||
| 372 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 373 | |||
| 374 | bool try_muzzy = !fully_decay | ||
| 375 | && pac_decay_ms_get(pac, extent_state_muzzy) != 0; | ||
| 376 | |||
| 377 | for (edata_t *edata = edata_list_inactive_first(decay_extents); edata != | ||
| 378 | NULL; edata = edata_list_inactive_first(decay_extents)) { | ||
| 379 | edata_list_inactive_remove(decay_extents, edata); | ||
| 380 | |||
| 381 | size_t size = edata_size_get(edata); | ||
| 382 | size_t npages = size >> LG_PAGE; | ||
| 383 | |||
| 384 | nmadvise++; | ||
| 385 | npurged += npages; | ||
| 386 | |||
| 387 | switch (ecache->state) { | ||
| 388 | case extent_state_active: | ||
| 389 | not_reached(); | ||
| 390 | case extent_state_dirty: | ||
| 391 | if (try_muzzy) { | ||
| 392 | err = extent_purge_lazy_wrapper(tsdn, ehooks, | ||
| 393 | edata, /* offset */ 0, size); | ||
| 394 | if (!err) { | ||
| 395 | ecache_dalloc(tsdn, pac, ehooks, | ||
| 396 | &pac->ecache_muzzy, edata); | ||
| 397 | break; | ||
| 398 | } | ||
| 399 | } | ||
| 400 | JEMALLOC_FALLTHROUGH; | ||
| 401 | case extent_state_muzzy: | ||
| 402 | extent_dalloc_wrapper(tsdn, pac, ehooks, edata); | ||
| 403 | nunmapped += npages; | ||
| 404 | break; | ||
| 405 | case extent_state_retained: | ||
| 406 | default: | ||
| 407 | not_reached(); | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | if (config_stats) { | ||
| 412 | LOCKEDINT_MTX_LOCK(tsdn, *pac->stats_mtx); | ||
| 413 | locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), | ||
| 414 | &decay_stats->npurge, 1); | ||
| 415 | locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), | ||
| 416 | &decay_stats->nmadvise, nmadvise); | ||
| 417 | locked_inc_u64(tsdn, LOCKEDINT_MTX(*pac->stats_mtx), | ||
| 418 | &decay_stats->purged, npurged); | ||
| 419 | LOCKEDINT_MTX_UNLOCK(tsdn, *pac->stats_mtx); | ||
| 420 | atomic_fetch_sub_zu(&pac->stats->pac_mapped, | ||
| 421 | nunmapped << LG_PAGE, ATOMIC_RELAXED); | ||
| 422 | } | ||
| 423 | |||
| 424 | return npurged; | ||
| 425 | } | ||
| 426 | |||
| 427 | /* | ||
| 428 | * npages_limit: Decay at most npages_decay_max pages without violating the | ||
| 429 | * invariant: (ecache_npages_get(ecache) >= npages_limit). We need an upper | ||
| 430 | * bound on number of pages in order to prevent unbounded growth (namely in | ||
| 431 | * stashed), otherwise unbounded new pages could be added to extents during the | ||
| 432 | * current decay run, so that the purging thread never finishes. | ||
| 433 | */ | ||
| 434 | static void | ||
| 435 | pac_decay_to_limit(tsdn_t *tsdn, pac_t *pac, decay_t *decay, | ||
| 436 | pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay, | ||
| 437 | size_t npages_limit, size_t npages_decay_max) { | ||
| 438 | witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn), | ||
| 439 | WITNESS_RANK_CORE, 1); | ||
| 440 | |||
| 441 | if (decay->purging || npages_decay_max == 0) { | ||
| 442 | return; | ||
| 443 | } | ||
| 444 | decay->purging = true; | ||
| 445 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 446 | |||
| 447 | edata_list_inactive_t decay_extents; | ||
| 448 | edata_list_inactive_init(&decay_extents); | ||
| 449 | size_t npurge = pac_stash_decayed(tsdn, pac, ecache, npages_limit, | ||
| 450 | npages_decay_max, &decay_extents); | ||
| 451 | if (npurge != 0) { | ||
| 452 | size_t npurged = pac_decay_stashed(tsdn, pac, decay, | ||
| 453 | decay_stats, ecache, fully_decay, &decay_extents); | ||
| 454 | assert(npurged == npurge); | ||
| 455 | } | ||
| 456 | |||
| 457 | malloc_mutex_lock(tsdn, &decay->mtx); | ||
| 458 | decay->purging = false; | ||
| 459 | } | ||
| 460 | |||
| 461 | void | ||
| 462 | pac_decay_all(tsdn_t *tsdn, pac_t *pac, decay_t *decay, | ||
| 463 | pac_decay_stats_t *decay_stats, ecache_t *ecache, bool fully_decay) { | ||
| 464 | malloc_mutex_assert_owner(tsdn, &decay->mtx); | ||
| 465 | pac_decay_to_limit(tsdn, pac, decay, decay_stats, ecache, fully_decay, | ||
| 466 | /* npages_limit */ 0, ecache_npages_get(ecache)); | ||
| 467 | } | ||
| 468 | |||
| 469 | static void | ||
| 470 | pac_decay_try_purge(tsdn_t *tsdn, pac_t *pac, decay_t *decay, | ||
| 471 | pac_decay_stats_t *decay_stats, ecache_t *ecache, | ||
| 472 | size_t current_npages, size_t npages_limit) { | ||
| 473 | if (current_npages > npages_limit) { | ||
| 474 | pac_decay_to_limit(tsdn, pac, decay, decay_stats, ecache, | ||
| 475 | /* fully_decay */ false, npages_limit, | ||
| 476 | current_npages - npages_limit); | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | bool | ||
| 481 | pac_maybe_decay_purge(tsdn_t *tsdn, pac_t *pac, decay_t *decay, | ||
| 482 | pac_decay_stats_t *decay_stats, ecache_t *ecache, | ||
| 483 | pac_purge_eagerness_t eagerness) { | ||
| 484 | malloc_mutex_assert_owner(tsdn, &decay->mtx); | ||
| 485 | |||
| 486 | /* Purge all or nothing if the option is disabled. */ | ||
| 487 | ssize_t decay_ms = decay_ms_read(decay); | ||
| 488 | if (decay_ms <= 0) { | ||
| 489 | if (decay_ms == 0) { | ||
| 490 | pac_decay_to_limit(tsdn, pac, decay, decay_stats, | ||
| 491 | ecache, /* fully_decay */ false, | ||
| 492 | /* npages_limit */ 0, ecache_npages_get(ecache)); | ||
| 493 | } | ||
| 494 | return false; | ||
| 495 | } | ||
| 496 | |||
| 497 | /* | ||
| 498 | * If the deadline has been reached, advance to the current epoch and | ||
| 499 | * purge to the new limit if necessary. Note that dirty pages created | ||
| 500 | * during the current epoch are not subject to purge until a future | ||
| 501 | * epoch, so as a result purging only happens during epoch advances, or | ||
| 502 | * being triggered by background threads (scheduled event). | ||
| 503 | */ | ||
| 504 | nstime_t time; | ||
| 505 | nstime_init_update(&time); | ||
| 506 | size_t npages_current = ecache_npages_get(ecache); | ||
| 507 | bool epoch_advanced = decay_maybe_advance_epoch(decay, &time, | ||
| 508 | npages_current); | ||
| 509 | if (eagerness == PAC_PURGE_ALWAYS | ||
| 510 | || (epoch_advanced && eagerness == PAC_PURGE_ON_EPOCH_ADVANCE)) { | ||
| 511 | size_t npages_limit = decay_npages_limit_get(decay); | ||
| 512 | pac_decay_try_purge(tsdn, pac, decay, decay_stats, ecache, | ||
| 513 | npages_current, npages_limit); | ||
| 514 | } | ||
| 515 | |||
| 516 | return epoch_advanced; | ||
| 517 | } | ||
| 518 | |||
| 519 | bool | ||
| 520 | pac_decay_ms_set(tsdn_t *tsdn, pac_t *pac, extent_state_t state, | ||
| 521 | ssize_t decay_ms, pac_purge_eagerness_t eagerness) { | ||
| 522 | decay_t *decay; | ||
| 523 | pac_decay_stats_t *decay_stats; | ||
| 524 | ecache_t *ecache; | ||
| 525 | pac_decay_data_get(pac, state, &decay, &decay_stats, &ecache); | ||
| 526 | |||
| 527 | if (!decay_ms_valid(decay_ms)) { | ||
| 528 | return true; | ||
| 529 | } | ||
| 530 | |||
| 531 | malloc_mutex_lock(tsdn, &decay->mtx); | ||
| 532 | /* | ||
| 533 | * Restart decay backlog from scratch, which may cause many dirty pages | ||
| 534 | * to be immediately purged. It would conceptually be possible to map | ||
| 535 | * the old backlog onto the new backlog, but there is no justification | ||
| 536 | * for such complexity since decay_ms changes are intended to be | ||
| 537 | * infrequent, either between the {-1, 0, >0} states, or a one-time | ||
| 538 | * arbitrary change during initial arena configuration. | ||
| 539 | */ | ||
| 540 | nstime_t cur_time; | ||
| 541 | nstime_init_update(&cur_time); | ||
| 542 | decay_reinit(decay, &cur_time, decay_ms); | ||
| 543 | pac_maybe_decay_purge(tsdn, pac, decay, decay_stats, ecache, eagerness); | ||
| 544 | malloc_mutex_unlock(tsdn, &decay->mtx); | ||
| 545 | |||
| 546 | return false; | ||
| 547 | } | ||
| 548 | |||
| 549 | ssize_t | ||
| 550 | pac_decay_ms_get(pac_t *pac, extent_state_t state) { | ||
| 551 | decay_t *decay; | ||
| 552 | pac_decay_stats_t *decay_stats; | ||
| 553 | ecache_t *ecache; | ||
| 554 | pac_decay_data_get(pac, state, &decay, &decay_stats, &ecache); | ||
| 555 | return decay_ms_read(decay); | ||
| 556 | } | ||
| 557 | |||
| 558 | void | ||
| 559 | pac_reset(tsdn_t *tsdn, pac_t *pac) { | ||
| 560 | /* | ||
| 561 | * No-op for now; purging is still done at the arena-level. It should | ||
| 562 | * get moved in here, though. | ||
| 563 | */ | ||
| 564 | (void)tsdn; | ||
| 565 | (void)pac; | ||
| 566 | } | ||
| 567 | |||
| 568 | void | ||
| 569 | pac_destroy(tsdn_t *tsdn, pac_t *pac) { | ||
| 570 | assert(ecache_npages_get(&pac->ecache_dirty) == 0); | ||
| 571 | assert(ecache_npages_get(&pac->ecache_muzzy) == 0); | ||
| 572 | /* | ||
| 573 | * Iterate over the retained extents and destroy them. This gives the | ||
| 574 | * extent allocator underlying the extent hooks an opportunity to unmap | ||
| 575 | * all retained memory without having to keep its own metadata | ||
| 576 | * structures. In practice, virtual memory for dss-allocated extents is | ||
| 577 | * leaked here, so best practice is to avoid dss for arenas to be | ||
| 578 | * destroyed, or provide custom extent hooks that track retained | ||
| 579 | * dss-based extents for later reuse. | ||
| 580 | */ | ||
| 581 | ehooks_t *ehooks = pac_ehooks_get(pac); | ||
| 582 | edata_t *edata; | ||
| 583 | while ((edata = ecache_evict(tsdn, pac, ehooks, | ||
| 584 | &pac->ecache_retained, 0)) != NULL) { | ||
| 585 | extent_destroy_wrapper(tsdn, pac, ehooks, edata); | ||
| 586 | } | ||
| 587 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/pages.c b/examples/redis-unstable/deps/jemalloc/src/pages.c deleted file mode 100644 index 8c83a7d..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/pages.c +++ /dev/null | |||
| @@ -1,824 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | |||
| 3 | #include "jemalloc/internal/pages.h" | ||
| 4 | |||
| 5 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 6 | |||
| 7 | #include "jemalloc/internal/assert.h" | ||
| 8 | #include "jemalloc/internal/malloc_io.h" | ||
| 9 | |||
| 10 | #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT | ||
| 11 | #include <sys/sysctl.h> | ||
| 12 | #ifdef __FreeBSD__ | ||
| 13 | #include <vm/vm_param.h> | ||
| 14 | #endif | ||
| 15 | #endif | ||
| 16 | #ifdef __NetBSD__ | ||
| 17 | #include <sys/bitops.h> /* ilog2 */ | ||
| 18 | #endif | ||
| 19 | #ifdef JEMALLOC_HAVE_VM_MAKE_TAG | ||
| 20 | #define PAGES_FD_TAG VM_MAKE_TAG(101U) | ||
| 21 | #else | ||
| 22 | #define PAGES_FD_TAG -1 | ||
| 23 | #endif | ||
| 24 | |||
| 25 | /******************************************************************************/ | ||
| 26 | /* Data. */ | ||
| 27 | |||
| 28 | /* Actual operating system page size, detected during bootstrap, <= PAGE. */ | ||
| 29 | static size_t os_page; | ||
| 30 | |||
| 31 | #ifndef _WIN32 | ||
| 32 | # define PAGES_PROT_COMMIT (PROT_READ | PROT_WRITE) | ||
| 33 | # define PAGES_PROT_DECOMMIT (PROT_NONE) | ||
| 34 | static int mmap_flags; | ||
| 35 | #endif | ||
| 36 | static bool os_overcommits; | ||
| 37 | |||
| 38 | const char *thp_mode_names[] = { | ||
| 39 | "default", | ||
| 40 | "always", | ||
| 41 | "never", | ||
| 42 | "not supported" | ||
| 43 | }; | ||
| 44 | thp_mode_t opt_thp = THP_MODE_DEFAULT; | ||
| 45 | thp_mode_t init_system_thp_mode; | ||
| 46 | |||
| 47 | /* Runtime support for lazy purge. Irrelevant when !pages_can_purge_lazy. */ | ||
| 48 | static bool pages_can_purge_lazy_runtime = true; | ||
| 49 | |||
| 50 | #ifdef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS | ||
| 51 | static int madvise_dont_need_zeros_is_faulty = -1; | ||
| 52 | /** | ||
| 53 | * Check that MADV_DONTNEED will actually zero pages on subsequent access. | ||
| 54 | * | ||
| 55 | * Since qemu does not support this, yet [1], and you can get very tricky | ||
| 56 | * assert if you will run program with jemalloc in use under qemu: | ||
| 57 | * | ||
| 58 | * <jemalloc>: ../contrib/jemalloc/src/extent.c:1195: Failed assertion: "p[i] == 0" | ||
| 59 | * | ||
| 60 | * [1]: https://patchwork.kernel.org/patch/10576637/ | ||
| 61 | */ | ||
| 62 | static int madvise_MADV_DONTNEED_zeroes_pages() | ||
| 63 | { | ||
| 64 | int works = -1; | ||
| 65 | size_t size = PAGE; | ||
| 66 | |||
| 67 | void * addr = mmap(NULL, size, PROT_READ|PROT_WRITE, | ||
| 68 | MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); | ||
| 69 | |||
| 70 | if (addr == MAP_FAILED) { | ||
| 71 | malloc_write("<jemalloc>: Cannot allocate memory for " | ||
| 72 | "MADV_DONTNEED check\n"); | ||
| 73 | if (opt_abort) { | ||
| 74 | abort(); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | memset(addr, 'A', size); | ||
| 79 | if (madvise(addr, size, MADV_DONTNEED) == 0) { | ||
| 80 | works = memchr(addr, 'A', size) == NULL; | ||
| 81 | } else { | ||
| 82 | /* | ||
| 83 | * If madvise() does not support MADV_DONTNEED, then we can | ||
| 84 | * call it anyway, and use it's return code. | ||
| 85 | */ | ||
| 86 | works = 1; | ||
| 87 | } | ||
| 88 | |||
| 89 | if (munmap(addr, size) != 0) { | ||
| 90 | malloc_write("<jemalloc>: Cannot deallocate memory for " | ||
| 91 | "MADV_DONTNEED check\n"); | ||
| 92 | if (opt_abort) { | ||
| 93 | abort(); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | return works; | ||
| 98 | } | ||
| 99 | #endif | ||
| 100 | |||
| 101 | /******************************************************************************/ | ||
| 102 | /* | ||
| 103 | * Function prototypes for static functions that are referenced prior to | ||
| 104 | * definition. | ||
| 105 | */ | ||
| 106 | |||
| 107 | static void os_pages_unmap(void *addr, size_t size); | ||
| 108 | |||
| 109 | /******************************************************************************/ | ||
| 110 | |||
| 111 | static void * | ||
| 112 | os_pages_map(void *addr, size_t size, size_t alignment, bool *commit) { | ||
| 113 | assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr); | ||
| 114 | assert(ALIGNMENT_CEILING(size, os_page) == size); | ||
| 115 | assert(size != 0); | ||
| 116 | |||
| 117 | if (os_overcommits) { | ||
| 118 | *commit = true; | ||
| 119 | } | ||
| 120 | |||
| 121 | void *ret; | ||
| 122 | #ifdef _WIN32 | ||
| 123 | /* | ||
| 124 | * If VirtualAlloc can't allocate at the given address when one is | ||
| 125 | * given, it fails and returns NULL. | ||
| 126 | */ | ||
| 127 | ret = VirtualAlloc(addr, size, MEM_RESERVE | (*commit ? MEM_COMMIT : 0), | ||
| 128 | PAGE_READWRITE); | ||
| 129 | #else | ||
| 130 | /* | ||
| 131 | * We don't use MAP_FIXED here, because it can cause the *replacement* | ||
| 132 | * of existing mappings, and we only want to create new mappings. | ||
| 133 | */ | ||
| 134 | { | ||
| 135 | #ifdef __NetBSD__ | ||
| 136 | /* | ||
| 137 | * On NetBSD PAGE for a platform is defined to the | ||
| 138 | * maximum page size of all machine architectures | ||
| 139 | * for that platform, so that we can use the same | ||
| 140 | * binaries across all machine architectures. | ||
| 141 | */ | ||
| 142 | if (alignment > os_page || PAGE > os_page) { | ||
| 143 | unsigned int a = ilog2(MAX(alignment, PAGE)); | ||
| 144 | mmap_flags |= MAP_ALIGNED(a); | ||
| 145 | } | ||
| 146 | #endif | ||
| 147 | int prot = *commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT; | ||
| 148 | |||
| 149 | ret = mmap(addr, size, prot, mmap_flags, PAGES_FD_TAG, 0); | ||
| 150 | } | ||
| 151 | assert(ret != NULL); | ||
| 152 | |||
| 153 | if (ret == MAP_FAILED) { | ||
| 154 | ret = NULL; | ||
| 155 | } else if (addr != NULL && ret != addr) { | ||
| 156 | /* | ||
| 157 | * We succeeded in mapping memory, but not in the right place. | ||
| 158 | */ | ||
| 159 | os_pages_unmap(ret, size); | ||
| 160 | ret = NULL; | ||
| 161 | } | ||
| 162 | #endif | ||
| 163 | assert(ret == NULL || (addr == NULL && ret != addr) || (addr != NULL && | ||
| 164 | ret == addr)); | ||
| 165 | return ret; | ||
| 166 | } | ||
| 167 | |||
| 168 | static void * | ||
| 169 | os_pages_trim(void *addr, size_t alloc_size, size_t leadsize, size_t size, | ||
| 170 | bool *commit) { | ||
| 171 | void *ret = (void *)((uintptr_t)addr + leadsize); | ||
| 172 | |||
| 173 | assert(alloc_size >= leadsize + size); | ||
| 174 | #ifdef _WIN32 | ||
| 175 | os_pages_unmap(addr, alloc_size); | ||
| 176 | void *new_addr = os_pages_map(ret, size, PAGE, commit); | ||
| 177 | if (new_addr == ret) { | ||
| 178 | return ret; | ||
| 179 | } | ||
| 180 | if (new_addr != NULL) { | ||
| 181 | os_pages_unmap(new_addr, size); | ||
| 182 | } | ||
| 183 | return NULL; | ||
| 184 | #else | ||
| 185 | size_t trailsize = alloc_size - leadsize - size; | ||
| 186 | |||
| 187 | if (leadsize != 0) { | ||
| 188 | os_pages_unmap(addr, leadsize); | ||
| 189 | } | ||
| 190 | if (trailsize != 0) { | ||
| 191 | os_pages_unmap((void *)((uintptr_t)ret + size), trailsize); | ||
| 192 | } | ||
| 193 | return ret; | ||
| 194 | #endif | ||
| 195 | } | ||
| 196 | |||
| 197 | static void | ||
| 198 | os_pages_unmap(void *addr, size_t size) { | ||
| 199 | assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr); | ||
| 200 | assert(ALIGNMENT_CEILING(size, os_page) == size); | ||
| 201 | |||
| 202 | #ifdef _WIN32 | ||
| 203 | if (VirtualFree(addr, 0, MEM_RELEASE) == 0) | ||
| 204 | #else | ||
| 205 | if (munmap(addr, size) == -1) | ||
| 206 | #endif | ||
| 207 | { | ||
| 208 | char buf[BUFERROR_BUF]; | ||
| 209 | |||
| 210 | buferror(get_errno(), buf, sizeof(buf)); | ||
| 211 | malloc_printf("<jemalloc>: Error in " | ||
| 212 | #ifdef _WIN32 | ||
| 213 | "VirtualFree" | ||
| 214 | #else | ||
| 215 | "munmap" | ||
| 216 | #endif | ||
| 217 | "(): %s\n", buf); | ||
| 218 | if (opt_abort) { | ||
| 219 | abort(); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | static void * | ||
| 225 | pages_map_slow(size_t size, size_t alignment, bool *commit) { | ||
| 226 | size_t alloc_size = size + alignment - os_page; | ||
| 227 | /* Beware size_t wrap-around. */ | ||
| 228 | if (alloc_size < size) { | ||
| 229 | return NULL; | ||
| 230 | } | ||
| 231 | |||
| 232 | void *ret; | ||
| 233 | do { | ||
| 234 | void *pages = os_pages_map(NULL, alloc_size, alignment, commit); | ||
| 235 | if (pages == NULL) { | ||
| 236 | return NULL; | ||
| 237 | } | ||
| 238 | size_t leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment) | ||
| 239 | - (uintptr_t)pages; | ||
| 240 | ret = os_pages_trim(pages, alloc_size, leadsize, size, commit); | ||
| 241 | } while (ret == NULL); | ||
| 242 | |||
| 243 | assert(ret != NULL); | ||
| 244 | assert(PAGE_ADDR2BASE(ret) == ret); | ||
| 245 | return ret; | ||
| 246 | } | ||
| 247 | |||
| 248 | void * | ||
| 249 | pages_map(void *addr, size_t size, size_t alignment, bool *commit) { | ||
| 250 | assert(alignment >= PAGE); | ||
| 251 | assert(ALIGNMENT_ADDR2BASE(addr, alignment) == addr); | ||
| 252 | |||
| 253 | #if defined(__FreeBSD__) && defined(MAP_EXCL) | ||
| 254 | /* | ||
| 255 | * FreeBSD has mechanisms both to mmap at specific address without | ||
| 256 | * touching existing mappings, and to mmap with specific alignment. | ||
| 257 | */ | ||
| 258 | { | ||
| 259 | if (os_overcommits) { | ||
| 260 | *commit = true; | ||
| 261 | } | ||
| 262 | |||
| 263 | int prot = *commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT; | ||
| 264 | int flags = mmap_flags; | ||
| 265 | |||
| 266 | if (addr != NULL) { | ||
| 267 | flags |= MAP_FIXED | MAP_EXCL; | ||
| 268 | } else { | ||
| 269 | unsigned alignment_bits = ffs_zu(alignment); | ||
| 270 | assert(alignment_bits > 0); | ||
| 271 | flags |= MAP_ALIGNED(alignment_bits); | ||
| 272 | } | ||
| 273 | |||
| 274 | void *ret = mmap(addr, size, prot, flags, -1, 0); | ||
| 275 | if (ret == MAP_FAILED) { | ||
| 276 | ret = NULL; | ||
| 277 | } | ||
| 278 | |||
| 279 | return ret; | ||
| 280 | } | ||
| 281 | #endif | ||
| 282 | /* | ||
| 283 | * Ideally, there would be a way to specify alignment to mmap() (like | ||
| 284 | * NetBSD has), but in the absence of such a feature, we have to work | ||
| 285 | * hard to efficiently create aligned mappings. The reliable, but | ||
| 286 | * slow method is to create a mapping that is over-sized, then trim the | ||
| 287 | * excess. However, that always results in one or two calls to | ||
| 288 | * os_pages_unmap(), and it can leave holes in the process's virtual | ||
| 289 | * memory map if memory grows downward. | ||
| 290 | * | ||
| 291 | * Optimistically try mapping precisely the right amount before falling | ||
| 292 | * back to the slow method, with the expectation that the optimistic | ||
| 293 | * approach works most of the time. | ||
| 294 | */ | ||
| 295 | |||
| 296 | void *ret = os_pages_map(addr, size, os_page, commit); | ||
| 297 | if (ret == NULL || ret == addr) { | ||
| 298 | return ret; | ||
| 299 | } | ||
| 300 | assert(addr == NULL); | ||
| 301 | if (ALIGNMENT_ADDR2OFFSET(ret, alignment) != 0) { | ||
| 302 | os_pages_unmap(ret, size); | ||
| 303 | return pages_map_slow(size, alignment, commit); | ||
| 304 | } | ||
| 305 | |||
| 306 | assert(PAGE_ADDR2BASE(ret) == ret); | ||
| 307 | return ret; | ||
| 308 | } | ||
| 309 | |||
| 310 | void | ||
| 311 | pages_unmap(void *addr, size_t size) { | ||
| 312 | assert(PAGE_ADDR2BASE(addr) == addr); | ||
| 313 | assert(PAGE_CEILING(size) == size); | ||
| 314 | |||
| 315 | os_pages_unmap(addr, size); | ||
| 316 | } | ||
| 317 | |||
| 318 | static bool | ||
| 319 | os_pages_commit(void *addr, size_t size, bool commit) { | ||
| 320 | assert(PAGE_ADDR2BASE(addr) == addr); | ||
| 321 | assert(PAGE_CEILING(size) == size); | ||
| 322 | |||
| 323 | #ifdef _WIN32 | ||
| 324 | return (commit ? (addr != VirtualAlloc(addr, size, MEM_COMMIT, | ||
| 325 | PAGE_READWRITE)) : (!VirtualFree(addr, size, MEM_DECOMMIT))); | ||
| 326 | #else | ||
| 327 | { | ||
| 328 | int prot = commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT; | ||
| 329 | void *result = mmap(addr, size, prot, mmap_flags | MAP_FIXED, | ||
| 330 | PAGES_FD_TAG, 0); | ||
| 331 | if (result == MAP_FAILED) { | ||
| 332 | return true; | ||
| 333 | } | ||
| 334 | if (result != addr) { | ||
| 335 | /* | ||
| 336 | * We succeeded in mapping memory, but not in the right | ||
| 337 | * place. | ||
| 338 | */ | ||
| 339 | os_pages_unmap(result, size); | ||
| 340 | return true; | ||
| 341 | } | ||
| 342 | return false; | ||
| 343 | } | ||
| 344 | #endif | ||
| 345 | } | ||
| 346 | |||
| 347 | static bool | ||
| 348 | pages_commit_impl(void *addr, size_t size, bool commit) { | ||
| 349 | if (os_overcommits) { | ||
| 350 | return true; | ||
| 351 | } | ||
| 352 | |||
| 353 | return os_pages_commit(addr, size, commit); | ||
| 354 | } | ||
| 355 | |||
| 356 | bool | ||
| 357 | pages_commit(void *addr, size_t size) { | ||
| 358 | return pages_commit_impl(addr, size, true); | ||
| 359 | } | ||
| 360 | |||
| 361 | bool | ||
| 362 | pages_decommit(void *addr, size_t size) { | ||
| 363 | return pages_commit_impl(addr, size, false); | ||
| 364 | } | ||
| 365 | |||
| 366 | void | ||
| 367 | pages_mark_guards(void *head, void *tail) { | ||
| 368 | assert(head != NULL || tail != NULL); | ||
| 369 | assert(head == NULL || tail == NULL || | ||
| 370 | (uintptr_t)head < (uintptr_t)tail); | ||
| 371 | #ifdef JEMALLOC_HAVE_MPROTECT | ||
| 372 | if (head != NULL) { | ||
| 373 | mprotect(head, PAGE, PROT_NONE); | ||
| 374 | } | ||
| 375 | if (tail != NULL) { | ||
| 376 | mprotect(tail, PAGE, PROT_NONE); | ||
| 377 | } | ||
| 378 | #else | ||
| 379 | /* Decommit sets to PROT_NONE / MEM_DECOMMIT. */ | ||
| 380 | if (head != NULL) { | ||
| 381 | os_pages_commit(head, PAGE, false); | ||
| 382 | } | ||
| 383 | if (tail != NULL) { | ||
| 384 | os_pages_commit(tail, PAGE, false); | ||
| 385 | } | ||
| 386 | #endif | ||
| 387 | } | ||
| 388 | |||
| 389 | void | ||
| 390 | pages_unmark_guards(void *head, void *tail) { | ||
| 391 | assert(head != NULL || tail != NULL); | ||
| 392 | assert(head == NULL || tail == NULL || | ||
| 393 | (uintptr_t)head < (uintptr_t)tail); | ||
| 394 | #ifdef JEMALLOC_HAVE_MPROTECT | ||
| 395 | bool head_and_tail = (head != NULL) && (tail != NULL); | ||
| 396 | size_t range = head_and_tail ? | ||
| 397 | (uintptr_t)tail - (uintptr_t)head + PAGE : | ||
| 398 | SIZE_T_MAX; | ||
| 399 | /* | ||
| 400 | * The amount of work that the kernel does in mprotect depends on the | ||
| 401 | * range argument. SC_LARGE_MINCLASS is an arbitrary threshold chosen | ||
| 402 | * to prevent kernel from doing too much work that would outweigh the | ||
| 403 | * savings of performing one less system call. | ||
| 404 | */ | ||
| 405 | bool ranged_mprotect = head_and_tail && range <= SC_LARGE_MINCLASS; | ||
| 406 | if (ranged_mprotect) { | ||
| 407 | mprotect(head, range, PROT_READ | PROT_WRITE); | ||
| 408 | } else { | ||
| 409 | if (head != NULL) { | ||
| 410 | mprotect(head, PAGE, PROT_READ | PROT_WRITE); | ||
| 411 | } | ||
| 412 | if (tail != NULL) { | ||
| 413 | mprotect(tail, PAGE, PROT_READ | PROT_WRITE); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | #else | ||
| 417 | if (head != NULL) { | ||
| 418 | os_pages_commit(head, PAGE, true); | ||
| 419 | } | ||
| 420 | if (tail != NULL) { | ||
| 421 | os_pages_commit(tail, PAGE, true); | ||
| 422 | } | ||
| 423 | #endif | ||
| 424 | } | ||
| 425 | |||
| 426 | bool | ||
| 427 | pages_purge_lazy(void *addr, size_t size) { | ||
| 428 | assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr); | ||
| 429 | assert(PAGE_CEILING(size) == size); | ||
| 430 | |||
| 431 | if (!pages_can_purge_lazy) { | ||
| 432 | return true; | ||
| 433 | } | ||
| 434 | if (!pages_can_purge_lazy_runtime) { | ||
| 435 | /* | ||
| 436 | * Built with lazy purge enabled, but detected it was not | ||
| 437 | * supported on the current system. | ||
| 438 | */ | ||
| 439 | return true; | ||
| 440 | } | ||
| 441 | |||
| 442 | #ifdef _WIN32 | ||
| 443 | VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE); | ||
| 444 | return false; | ||
| 445 | #elif defined(JEMALLOC_PURGE_MADVISE_FREE) | ||
| 446 | return (madvise(addr, size, | ||
| 447 | # ifdef MADV_FREE | ||
| 448 | MADV_FREE | ||
| 449 | # else | ||
| 450 | JEMALLOC_MADV_FREE | ||
| 451 | # endif | ||
| 452 | ) != 0); | ||
| 453 | #elif defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \ | ||
| 454 | !defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS) | ||
| 455 | return (madvise(addr, size, MADV_DONTNEED) != 0); | ||
| 456 | #elif defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED) && \ | ||
| 457 | !defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED_ZEROS) | ||
| 458 | return (posix_madvise(addr, size, POSIX_MADV_DONTNEED) != 0); | ||
| 459 | #else | ||
| 460 | not_reached(); | ||
| 461 | #endif | ||
| 462 | } | ||
| 463 | |||
| 464 | bool | ||
| 465 | pages_purge_forced(void *addr, size_t size) { | ||
| 466 | assert(PAGE_ADDR2BASE(addr) == addr); | ||
| 467 | assert(PAGE_CEILING(size) == size); | ||
| 468 | |||
| 469 | if (!pages_can_purge_forced) { | ||
| 470 | return true; | ||
| 471 | } | ||
| 472 | |||
| 473 | #if defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \ | ||
| 474 | defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS) | ||
| 475 | return (unlikely(madvise_dont_need_zeros_is_faulty) || | ||
| 476 | madvise(addr, size, MADV_DONTNEED) != 0); | ||
| 477 | #elif defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED) && \ | ||
| 478 | defined(JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED_ZEROS) | ||
| 479 | return (unlikely(madvise_dont_need_zeros_is_faulty) || | ||
| 480 | posix_madvise(addr, size, POSIX_MADV_DONTNEED) != 0); | ||
| 481 | #elif defined(JEMALLOC_MAPS_COALESCE) | ||
| 482 | /* Try to overlay a new demand-zeroed mapping. */ | ||
| 483 | return pages_commit(addr, size); | ||
| 484 | #else | ||
| 485 | not_reached(); | ||
| 486 | #endif | ||
| 487 | } | ||
| 488 | |||
| 489 | static bool | ||
| 490 | pages_huge_impl(void *addr, size_t size, bool aligned) { | ||
| 491 | if (aligned) { | ||
| 492 | assert(HUGEPAGE_ADDR2BASE(addr) == addr); | ||
| 493 | assert(HUGEPAGE_CEILING(size) == size); | ||
| 494 | } | ||
| 495 | #if defined(JEMALLOC_HAVE_MADVISE_HUGE) | ||
| 496 | return (madvise(addr, size, MADV_HUGEPAGE) != 0); | ||
| 497 | #elif defined(JEMALLOC_HAVE_MEMCNTL) | ||
| 498 | struct memcntl_mha m = {0}; | ||
| 499 | m.mha_cmd = MHA_MAPSIZE_VA; | ||
| 500 | m.mha_pagesize = HUGEPAGE; | ||
| 501 | return (memcntl(addr, size, MC_HAT_ADVISE, (caddr_t)&m, 0, 0) == 0); | ||
| 502 | #else | ||
| 503 | return true; | ||
| 504 | #endif | ||
| 505 | } | ||
| 506 | |||
| 507 | bool | ||
| 508 | pages_huge(void *addr, size_t size) { | ||
| 509 | return pages_huge_impl(addr, size, true); | ||
| 510 | } | ||
| 511 | |||
| 512 | static bool | ||
| 513 | pages_huge_unaligned(void *addr, size_t size) { | ||
| 514 | return pages_huge_impl(addr, size, false); | ||
| 515 | } | ||
| 516 | |||
| 517 | static bool | ||
| 518 | pages_nohuge_impl(void *addr, size_t size, bool aligned) { | ||
| 519 | if (aligned) { | ||
| 520 | assert(HUGEPAGE_ADDR2BASE(addr) == addr); | ||
| 521 | assert(HUGEPAGE_CEILING(size) == size); | ||
| 522 | } | ||
| 523 | |||
| 524 | #ifdef JEMALLOC_HAVE_MADVISE_HUGE | ||
| 525 | return (madvise(addr, size, MADV_NOHUGEPAGE) != 0); | ||
| 526 | #else | ||
| 527 | return false; | ||
| 528 | #endif | ||
| 529 | } | ||
| 530 | |||
| 531 | bool | ||
| 532 | pages_nohuge(void *addr, size_t size) { | ||
| 533 | return pages_nohuge_impl(addr, size, true); | ||
| 534 | } | ||
| 535 | |||
| 536 | static bool | ||
| 537 | pages_nohuge_unaligned(void *addr, size_t size) { | ||
| 538 | return pages_nohuge_impl(addr, size, false); | ||
| 539 | } | ||
| 540 | |||
| 541 | bool | ||
| 542 | pages_dontdump(void *addr, size_t size) { | ||
| 543 | assert(PAGE_ADDR2BASE(addr) == addr); | ||
| 544 | assert(PAGE_CEILING(size) == size); | ||
| 545 | #if defined(JEMALLOC_MADVISE_DONTDUMP) | ||
| 546 | return madvise(addr, size, MADV_DONTDUMP) != 0; | ||
| 547 | #elif defined(JEMALLOC_MADVISE_NOCORE) | ||
| 548 | return madvise(addr, size, MADV_NOCORE) != 0; | ||
| 549 | #else | ||
| 550 | return false; | ||
| 551 | #endif | ||
| 552 | } | ||
| 553 | |||
| 554 | bool | ||
| 555 | pages_dodump(void *addr, size_t size) { | ||
| 556 | assert(PAGE_ADDR2BASE(addr) == addr); | ||
| 557 | assert(PAGE_CEILING(size) == size); | ||
| 558 | #if defined(JEMALLOC_MADVISE_DONTDUMP) | ||
| 559 | return madvise(addr, size, MADV_DODUMP) != 0; | ||
| 560 | #elif defined(JEMALLOC_MADVISE_NOCORE) | ||
| 561 | return madvise(addr, size, MADV_CORE) != 0; | ||
| 562 | #else | ||
| 563 | return false; | ||
| 564 | #endif | ||
| 565 | } | ||
| 566 | |||
| 567 | |||
| 568 | static size_t | ||
| 569 | os_page_detect(void) { | ||
| 570 | #ifdef _WIN32 | ||
| 571 | SYSTEM_INFO si; | ||
| 572 | GetSystemInfo(&si); | ||
| 573 | return si.dwPageSize; | ||
| 574 | #elif defined(__FreeBSD__) | ||
| 575 | /* | ||
| 576 | * This returns the value obtained from | ||
| 577 | * the auxv vector, avoiding a syscall. | ||
| 578 | */ | ||
| 579 | return getpagesize(); | ||
| 580 | #else | ||
| 581 | long result = sysconf(_SC_PAGESIZE); | ||
| 582 | if (result == -1) { | ||
| 583 | return LG_PAGE; | ||
| 584 | } | ||
| 585 | return (size_t)result; | ||
| 586 | #endif | ||
| 587 | } | ||
| 588 | |||
| 589 | #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT | ||
| 590 | static bool | ||
| 591 | os_overcommits_sysctl(void) { | ||
| 592 | int vm_overcommit; | ||
| 593 | size_t sz; | ||
| 594 | |||
| 595 | sz = sizeof(vm_overcommit); | ||
| 596 | #if defined(__FreeBSD__) && defined(VM_OVERCOMMIT) | ||
| 597 | int mib[2]; | ||
| 598 | |||
| 599 | mib[0] = CTL_VM; | ||
| 600 | mib[1] = VM_OVERCOMMIT; | ||
| 601 | if (sysctl(mib, 2, &vm_overcommit, &sz, NULL, 0) != 0) { | ||
| 602 | return false; /* Error. */ | ||
| 603 | } | ||
| 604 | #else | ||
| 605 | if (sysctlbyname("vm.overcommit", &vm_overcommit, &sz, NULL, 0) != 0) { | ||
| 606 | return false; /* Error. */ | ||
| 607 | } | ||
| 608 | #endif | ||
| 609 | |||
| 610 | return ((vm_overcommit & 0x3) == 0); | ||
| 611 | } | ||
| 612 | #endif | ||
| 613 | |||
| 614 | #ifdef JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY | ||
| 615 | /* | ||
| 616 | * Use syscall(2) rather than {open,read,close}(2) when possible to avoid | ||
| 617 | * reentry during bootstrapping if another library has interposed system call | ||
| 618 | * wrappers. | ||
| 619 | */ | ||
| 620 | static bool | ||
| 621 | os_overcommits_proc(void) { | ||
| 622 | int fd; | ||
| 623 | char buf[1]; | ||
| 624 | |||
| 625 | #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_open) | ||
| 626 | #if defined(O_CLOEXEC) | ||
| 627 | fd = (int)syscall(SYS_open, "/proc/sys/vm/overcommit_memory", O_RDONLY | | ||
| 628 | O_CLOEXEC); | ||
| 629 | #else | ||
| 630 | fd = (int)syscall(SYS_open, "/proc/sys/vm/overcommit_memory", O_RDONLY); | ||
| 631 | if (fd != -1) { | ||
| 632 | fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); | ||
| 633 | } | ||
| 634 | #endif | ||
| 635 | #elif defined(JEMALLOC_USE_SYSCALL) && defined(SYS_openat) | ||
| 636 | #if defined(O_CLOEXEC) | ||
| 637 | fd = (int)syscall(SYS_openat, | ||
| 638 | AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC); | ||
| 639 | #else | ||
| 640 | fd = (int)syscall(SYS_openat, | ||
| 641 | AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY); | ||
| 642 | if (fd != -1) { | ||
| 643 | fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); | ||
| 644 | } | ||
| 645 | #endif | ||
| 646 | #else | ||
| 647 | #if defined(O_CLOEXEC) | ||
| 648 | fd = open("/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC); | ||
| 649 | #else | ||
| 650 | fd = open("/proc/sys/vm/overcommit_memory", O_RDONLY); | ||
| 651 | if (fd != -1) { | ||
| 652 | fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); | ||
| 653 | } | ||
| 654 | #endif | ||
| 655 | #endif | ||
| 656 | |||
| 657 | if (fd == -1) { | ||
| 658 | return false; /* Error. */ | ||
| 659 | } | ||
| 660 | |||
| 661 | ssize_t nread = malloc_read_fd(fd, &buf, sizeof(buf)); | ||
| 662 | #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_close) | ||
| 663 | syscall(SYS_close, fd); | ||
| 664 | #else | ||
| 665 | close(fd); | ||
| 666 | #endif | ||
| 667 | |||
| 668 | if (nread < 1) { | ||
| 669 | return false; /* Error. */ | ||
| 670 | } | ||
| 671 | /* | ||
| 672 | * /proc/sys/vm/overcommit_memory meanings: | ||
| 673 | * 0: Heuristic overcommit. | ||
| 674 | * 1: Always overcommit. | ||
| 675 | * 2: Never overcommit. | ||
| 676 | */ | ||
| 677 | return (buf[0] == '0' || buf[0] == '1'); | ||
| 678 | } | ||
| 679 | #endif | ||
| 680 | |||
| 681 | void | ||
| 682 | pages_set_thp_state (void *ptr, size_t size) { | ||
| 683 | if (opt_thp == thp_mode_default || opt_thp == init_system_thp_mode) { | ||
| 684 | return; | ||
| 685 | } | ||
| 686 | assert(opt_thp != thp_mode_not_supported && | ||
| 687 | init_system_thp_mode != thp_mode_not_supported); | ||
| 688 | |||
| 689 | if (opt_thp == thp_mode_always | ||
| 690 | && init_system_thp_mode != thp_mode_never) { | ||
| 691 | assert(init_system_thp_mode == thp_mode_default); | ||
| 692 | pages_huge_unaligned(ptr, size); | ||
| 693 | } else if (opt_thp == thp_mode_never) { | ||
| 694 | assert(init_system_thp_mode == thp_mode_default || | ||
| 695 | init_system_thp_mode == thp_mode_always); | ||
| 696 | pages_nohuge_unaligned(ptr, size); | ||
| 697 | } | ||
| 698 | } | ||
| 699 | |||
| 700 | static void | ||
| 701 | init_thp_state(void) { | ||
| 702 | if (!have_madvise_huge && !have_memcntl) { | ||
| 703 | if (metadata_thp_enabled() && opt_abort) { | ||
| 704 | malloc_write("<jemalloc>: no MADV_HUGEPAGE support\n"); | ||
| 705 | abort(); | ||
| 706 | } | ||
| 707 | goto label_error; | ||
| 708 | } | ||
| 709 | #if defined(JEMALLOC_HAVE_MADVISE_HUGE) | ||
| 710 | static const char sys_state_madvise[] = "always [madvise] never\n"; | ||
| 711 | static const char sys_state_always[] = "[always] madvise never\n"; | ||
| 712 | static const char sys_state_never[] = "always madvise [never]\n"; | ||
| 713 | char buf[sizeof(sys_state_madvise)]; | ||
| 714 | |||
| 715 | #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_open) | ||
| 716 | int fd = (int)syscall(SYS_open, | ||
| 717 | "/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY); | ||
| 718 | #elif defined(JEMALLOC_USE_SYSCALL) && defined(SYS_openat) | ||
| 719 | int fd = (int)syscall(SYS_openat, | ||
| 720 | AT_FDCWD, "/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY); | ||
| 721 | #else | ||
| 722 | int fd = open("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY); | ||
| 723 | #endif | ||
| 724 | if (fd == -1) { | ||
| 725 | goto label_error; | ||
| 726 | } | ||
| 727 | |||
| 728 | ssize_t nread = malloc_read_fd(fd, &buf, sizeof(buf)); | ||
| 729 | #if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_close) | ||
| 730 | syscall(SYS_close, fd); | ||
| 731 | #else | ||
| 732 | close(fd); | ||
| 733 | #endif | ||
| 734 | |||
| 735 | if (nread < 0) { | ||
| 736 | goto label_error; | ||
| 737 | } | ||
| 738 | |||
| 739 | if (strncmp(buf, sys_state_madvise, (size_t)nread) == 0) { | ||
| 740 | init_system_thp_mode = thp_mode_default; | ||
| 741 | } else if (strncmp(buf, sys_state_always, (size_t)nread) == 0) { | ||
| 742 | init_system_thp_mode = thp_mode_always; | ||
| 743 | } else if (strncmp(buf, sys_state_never, (size_t)nread) == 0) { | ||
| 744 | init_system_thp_mode = thp_mode_never; | ||
| 745 | } else { | ||
| 746 | goto label_error; | ||
| 747 | } | ||
| 748 | return; | ||
| 749 | #elif defined(JEMALLOC_HAVE_MEMCNTL) | ||
| 750 | init_system_thp_mode = thp_mode_default; | ||
| 751 | return; | ||
| 752 | #endif | ||
| 753 | label_error: | ||
| 754 | opt_thp = init_system_thp_mode = thp_mode_not_supported; | ||
| 755 | } | ||
| 756 | |||
| 757 | bool | ||
| 758 | pages_boot(void) { | ||
| 759 | os_page = os_page_detect(); | ||
| 760 | if (os_page > PAGE) { | ||
| 761 | malloc_write("<jemalloc>: Unsupported system page size\n"); | ||
| 762 | if (opt_abort) { | ||
| 763 | abort(); | ||
| 764 | } | ||
| 765 | return true; | ||
| 766 | } | ||
| 767 | |||
| 768 | #ifdef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS | ||
| 769 | if (!opt_trust_madvise) { | ||
| 770 | madvise_dont_need_zeros_is_faulty = !madvise_MADV_DONTNEED_zeroes_pages(); | ||
| 771 | if (madvise_dont_need_zeros_is_faulty) { | ||
| 772 | malloc_write("<jemalloc>: MADV_DONTNEED does not work (memset will be used instead)\n"); | ||
| 773 | malloc_write("<jemalloc>: (This is the expected behaviour if you are running under QEMU)\n"); | ||
| 774 | } | ||
| 775 | } else { | ||
| 776 | /* In case opt_trust_madvise is disable, | ||
| 777 | * do not do runtime check */ | ||
| 778 | madvise_dont_need_zeros_is_faulty = 0; | ||
| 779 | } | ||
| 780 | #endif | ||
| 781 | |||
| 782 | #ifndef _WIN32 | ||
| 783 | mmap_flags = MAP_PRIVATE | MAP_ANON; | ||
| 784 | #endif | ||
| 785 | |||
| 786 | #ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT | ||
| 787 | os_overcommits = os_overcommits_sysctl(); | ||
| 788 | #elif defined(JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY) | ||
| 789 | os_overcommits = os_overcommits_proc(); | ||
| 790 | # ifdef MAP_NORESERVE | ||
| 791 | if (os_overcommits) { | ||
| 792 | mmap_flags |= MAP_NORESERVE; | ||
| 793 | } | ||
| 794 | # endif | ||
| 795 | #elif defined(__NetBSD__) | ||
| 796 | os_overcommits = true; | ||
| 797 | #else | ||
| 798 | os_overcommits = false; | ||
| 799 | #endif | ||
| 800 | |||
| 801 | init_thp_state(); | ||
| 802 | |||
| 803 | #ifdef __FreeBSD__ | ||
| 804 | /* | ||
| 805 | * FreeBSD doesn't need the check; madvise(2) is known to work. | ||
| 806 | */ | ||
| 807 | #else | ||
| 808 | /* Detect lazy purge runtime support. */ | ||
| 809 | if (pages_can_purge_lazy) { | ||
| 810 | bool committed = false; | ||
| 811 | void *madv_free_page = os_pages_map(NULL, PAGE, PAGE, &committed); | ||
| 812 | if (madv_free_page == NULL) { | ||
| 813 | return true; | ||
| 814 | } | ||
| 815 | assert(pages_can_purge_lazy_runtime); | ||
| 816 | if (pages_purge_lazy(madv_free_page, PAGE)) { | ||
| 817 | pages_can_purge_lazy_runtime = false; | ||
| 818 | } | ||
| 819 | os_pages_unmap(madv_free_page, PAGE); | ||
| 820 | } | ||
| 821 | #endif | ||
| 822 | |||
| 823 | return false; | ||
| 824 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/pai.c b/examples/redis-unstable/deps/jemalloc/src/pai.c deleted file mode 100644 index 45c8772..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/pai.c +++ /dev/null | |||
| @@ -1,31 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | size_t | ||
| 5 | pai_alloc_batch_default(tsdn_t *tsdn, pai_t *self, size_t size, size_t nallocs, | ||
| 6 | edata_list_active_t *results, bool *deferred_work_generated) { | ||
| 7 | for (size_t i = 0; i < nallocs; i++) { | ||
| 8 | bool deferred_by_alloc = false; | ||
| 9 | edata_t *edata = pai_alloc(tsdn, self, size, PAGE, | ||
| 10 | /* zero */ false, /* guarded */ false, | ||
| 11 | /* frequent_reuse */ false, &deferred_by_alloc); | ||
| 12 | *deferred_work_generated |= deferred_by_alloc; | ||
| 13 | if (edata == NULL) { | ||
| 14 | return i; | ||
| 15 | } | ||
| 16 | edata_list_active_append(results, edata); | ||
| 17 | } | ||
| 18 | return nallocs; | ||
| 19 | } | ||
| 20 | |||
| 21 | void | ||
| 22 | pai_dalloc_batch_default(tsdn_t *tsdn, pai_t *self, | ||
| 23 | edata_list_active_t *list, bool *deferred_work_generated) { | ||
| 24 | edata_t *edata; | ||
| 25 | while ((edata = edata_list_active_first(list)) != NULL) { | ||
| 26 | bool deferred_by_dalloc = false; | ||
| 27 | edata_list_active_remove(list, edata); | ||
| 28 | pai_dalloc(tsdn, self, edata, &deferred_by_dalloc); | ||
| 29 | *deferred_work_generated |= deferred_by_dalloc; | ||
| 30 | } | ||
| 31 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/peak_event.c b/examples/redis-unstable/deps/jemalloc/src/peak_event.c deleted file mode 100644 index 4093fbc..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/peak_event.c +++ /dev/null | |||
| @@ -1,82 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/peak_event.h" | ||
| 5 | |||
| 6 | #include "jemalloc/internal/activity_callback.h" | ||
| 7 | #include "jemalloc/internal/peak.h" | ||
| 8 | |||
| 9 | /* | ||
| 10 | * Update every 64K by default. We're not exposing this as a configuration | ||
| 11 | * option for now; we don't want to bind ourselves too tightly to any particular | ||
| 12 | * performance requirements for small values, or guarantee that we'll even be | ||
| 13 | * able to provide fine-grained accuracy. | ||
| 14 | */ | ||
| 15 | #define PEAK_EVENT_WAIT (64 * 1024) | ||
| 16 | |||
| 17 | /* Update the peak with current tsd state. */ | ||
| 18 | void | ||
| 19 | peak_event_update(tsd_t *tsd) { | ||
| 20 | uint64_t alloc = tsd_thread_allocated_get(tsd); | ||
| 21 | uint64_t dalloc = tsd_thread_deallocated_get(tsd); | ||
| 22 | peak_t *peak = tsd_peakp_get(tsd); | ||
| 23 | peak_update(peak, alloc, dalloc); | ||
| 24 | } | ||
| 25 | |||
| 26 | static void | ||
| 27 | peak_event_activity_callback(tsd_t *tsd) { | ||
| 28 | activity_callback_thunk_t *thunk = tsd_activity_callback_thunkp_get( | ||
| 29 | tsd); | ||
| 30 | uint64_t alloc = tsd_thread_allocated_get(tsd); | ||
| 31 | uint64_t dalloc = tsd_thread_deallocated_get(tsd); | ||
| 32 | if (thunk->callback != NULL) { | ||
| 33 | thunk->callback(thunk->uctx, alloc, dalloc); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | /* Set current state to zero. */ | ||
| 38 | void | ||
| 39 | peak_event_zero(tsd_t *tsd) { | ||
| 40 | uint64_t alloc = tsd_thread_allocated_get(tsd); | ||
| 41 | uint64_t dalloc = tsd_thread_deallocated_get(tsd); | ||
| 42 | peak_t *peak = tsd_peakp_get(tsd); | ||
| 43 | peak_set_zero(peak, alloc, dalloc); | ||
| 44 | } | ||
| 45 | |||
| 46 | uint64_t | ||
| 47 | peak_event_max(tsd_t *tsd) { | ||
| 48 | peak_t *peak = tsd_peakp_get(tsd); | ||
| 49 | return peak_max(peak); | ||
| 50 | } | ||
| 51 | |||
| 52 | uint64_t | ||
| 53 | peak_alloc_new_event_wait(tsd_t *tsd) { | ||
| 54 | return PEAK_EVENT_WAIT; | ||
| 55 | } | ||
| 56 | |||
| 57 | uint64_t | ||
| 58 | peak_alloc_postponed_event_wait(tsd_t *tsd) { | ||
| 59 | return TE_MIN_START_WAIT; | ||
| 60 | } | ||
| 61 | |||
| 62 | void | ||
| 63 | peak_alloc_event_handler(tsd_t *tsd, uint64_t elapsed) { | ||
| 64 | peak_event_update(tsd); | ||
| 65 | peak_event_activity_callback(tsd); | ||
| 66 | } | ||
| 67 | |||
| 68 | uint64_t | ||
| 69 | peak_dalloc_new_event_wait(tsd_t *tsd) { | ||
| 70 | return PEAK_EVENT_WAIT; | ||
| 71 | } | ||
| 72 | |||
| 73 | uint64_t | ||
| 74 | peak_dalloc_postponed_event_wait(tsd_t *tsd) { | ||
| 75 | return TE_MIN_START_WAIT; | ||
| 76 | } | ||
| 77 | |||
| 78 | void | ||
| 79 | peak_dalloc_event_handler(tsd_t *tsd, uint64_t elapsed) { | ||
| 80 | peak_event_update(tsd); | ||
| 81 | peak_event_activity_callback(tsd); | ||
| 82 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/prof.c b/examples/redis-unstable/deps/jemalloc/src/prof.c deleted file mode 100644 index 7a6d5d5..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/prof.c +++ /dev/null | |||
| @@ -1,789 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/ctl.h" | ||
| 5 | #include "jemalloc/internal/assert.h" | ||
| 6 | #include "jemalloc/internal/mutex.h" | ||
| 7 | #include "jemalloc/internal/counter.h" | ||
| 8 | #include "jemalloc/internal/prof_data.h" | ||
| 9 | #include "jemalloc/internal/prof_log.h" | ||
| 10 | #include "jemalloc/internal/prof_recent.h" | ||
| 11 | #include "jemalloc/internal/prof_stats.h" | ||
| 12 | #include "jemalloc/internal/prof_sys.h" | ||
| 13 | #include "jemalloc/internal/prof_hook.h" | ||
| 14 | #include "jemalloc/internal/thread_event.h" | ||
| 15 | |||
| 16 | /* | ||
| 17 | * This file implements the profiling "APIs" needed by other parts of jemalloc, | ||
| 18 | * and also manages the relevant "operational" data, mainly options and mutexes; | ||
| 19 | * the core profiling data structures are encapsulated in prof_data.c. | ||
| 20 | */ | ||
| 21 | |||
| 22 | /******************************************************************************/ | ||
| 23 | |||
| 24 | /* Data. */ | ||
| 25 | |||
| 26 | bool opt_prof = false; | ||
| 27 | bool opt_prof_active = true; | ||
| 28 | bool opt_prof_thread_active_init = true; | ||
| 29 | size_t opt_lg_prof_sample = LG_PROF_SAMPLE_DEFAULT; | ||
| 30 | ssize_t opt_lg_prof_interval = LG_PROF_INTERVAL_DEFAULT; | ||
| 31 | bool opt_prof_gdump = false; | ||
| 32 | bool opt_prof_final = false; | ||
| 33 | bool opt_prof_leak = false; | ||
| 34 | bool opt_prof_leak_error = false; | ||
| 35 | bool opt_prof_accum = false; | ||
| 36 | char opt_prof_prefix[PROF_DUMP_FILENAME_LEN]; | ||
| 37 | bool opt_prof_sys_thread_name = false; | ||
| 38 | bool opt_prof_unbias = true; | ||
| 39 | |||
| 40 | /* Accessed via prof_sample_event_handler(). */ | ||
| 41 | static counter_accum_t prof_idump_accumulated; | ||
| 42 | |||
| 43 | /* | ||
| 44 | * Initialized as opt_prof_active, and accessed via | ||
| 45 | * prof_active_[gs]et{_unlocked,}(). | ||
| 46 | */ | ||
| 47 | bool prof_active_state; | ||
| 48 | static malloc_mutex_t prof_active_mtx; | ||
| 49 | |||
| 50 | /* | ||
| 51 | * Initialized as opt_prof_thread_active_init, and accessed via | ||
| 52 | * prof_thread_active_init_[gs]et(). | ||
| 53 | */ | ||
| 54 | static bool prof_thread_active_init; | ||
| 55 | static malloc_mutex_t prof_thread_active_init_mtx; | ||
| 56 | |||
| 57 | /* | ||
| 58 | * Initialized as opt_prof_gdump, and accessed via | ||
| 59 | * prof_gdump_[gs]et{_unlocked,}(). | ||
| 60 | */ | ||
| 61 | bool prof_gdump_val; | ||
| 62 | static malloc_mutex_t prof_gdump_mtx; | ||
| 63 | |||
| 64 | uint64_t prof_interval = 0; | ||
| 65 | |||
| 66 | size_t lg_prof_sample; | ||
| 67 | |||
| 68 | static uint64_t next_thr_uid; | ||
| 69 | static malloc_mutex_t next_thr_uid_mtx; | ||
| 70 | |||
| 71 | /* Do not dump any profiles until bootstrapping is complete. */ | ||
| 72 | bool prof_booted = false; | ||
| 73 | |||
| 74 | /* Logically a prof_backtrace_hook_t. */ | ||
| 75 | atomic_p_t prof_backtrace_hook; | ||
| 76 | |||
| 77 | /* Logically a prof_dump_hook_t. */ | ||
| 78 | atomic_p_t prof_dump_hook; | ||
| 79 | |||
| 80 | /******************************************************************************/ | ||
| 81 | |||
| 82 | void | ||
| 83 | prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 84 | cassert(config_prof); | ||
| 85 | |||
| 86 | if (tsd_reentrancy_level_get(tsd) > 0) { | ||
| 87 | assert((uintptr_t)tctx == (uintptr_t)1U); | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | if ((uintptr_t)tctx > (uintptr_t)1U) { | ||
| 92 | malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 93 | tctx->prepared = false; | ||
| 94 | prof_tctx_try_destroy(tsd, tctx); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | void | ||
| 99 | prof_malloc_sample_object(tsd_t *tsd, const void *ptr, size_t size, | ||
| 100 | size_t usize, prof_tctx_t *tctx) { | ||
| 101 | cassert(config_prof); | ||
| 102 | |||
| 103 | if (opt_prof_sys_thread_name) { | ||
| 104 | prof_sys_thread_name_fetch(tsd); | ||
| 105 | } | ||
| 106 | |||
| 107 | edata_t *edata = emap_edata_lookup(tsd_tsdn(tsd), &arena_emap_global, | ||
| 108 | ptr); | ||
| 109 | prof_info_set(tsd, edata, tctx, size); | ||
| 110 | |||
| 111 | szind_t szind = sz_size2index(usize); | ||
| 112 | |||
| 113 | malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 114 | /* | ||
| 115 | * We need to do these map lookups while holding the lock, to avoid the | ||
| 116 | * possibility of races with prof_reset calls, which update the map and | ||
| 117 | * then acquire the lock. This actually still leaves a data race on the | ||
| 118 | * contents of the unbias map, but we have not yet gone through and | ||
| 119 | * atomic-ified the prof module, and compilers are not yet causing us | ||
| 120 | * issues. The key thing is to make sure that, if we read garbage data, | ||
| 121 | * the prof_reset call is about to mark our tctx as expired before any | ||
| 122 | * dumping of our corrupted output is attempted. | ||
| 123 | */ | ||
| 124 | size_t shifted_unbiased_cnt = prof_shifted_unbiased_cnt[szind]; | ||
| 125 | size_t unbiased_bytes = prof_unbiased_sz[szind]; | ||
| 126 | tctx->cnts.curobjs++; | ||
| 127 | tctx->cnts.curobjs_shifted_unbiased += shifted_unbiased_cnt; | ||
| 128 | tctx->cnts.curbytes += usize; | ||
| 129 | tctx->cnts.curbytes_unbiased += unbiased_bytes; | ||
| 130 | if (opt_prof_accum) { | ||
| 131 | tctx->cnts.accumobjs++; | ||
| 132 | tctx->cnts.accumobjs_shifted_unbiased += shifted_unbiased_cnt; | ||
| 133 | tctx->cnts.accumbytes += usize; | ||
| 134 | tctx->cnts.accumbytes_unbiased += unbiased_bytes; | ||
| 135 | } | ||
| 136 | bool record_recent = prof_recent_alloc_prepare(tsd, tctx); | ||
| 137 | tctx->prepared = false; | ||
| 138 | malloc_mutex_unlock(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 139 | if (record_recent) { | ||
| 140 | assert(tctx == edata_prof_tctx_get(edata)); | ||
| 141 | prof_recent_alloc(tsd, edata, size, usize); | ||
| 142 | } | ||
| 143 | |||
| 144 | if (opt_prof_stats) { | ||
| 145 | prof_stats_inc(tsd, szind, size); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | void | ||
| 150 | prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_info_t *prof_info) { | ||
| 151 | cassert(config_prof); | ||
| 152 | |||
| 153 | assert(prof_info != NULL); | ||
| 154 | prof_tctx_t *tctx = prof_info->alloc_tctx; | ||
| 155 | assert((uintptr_t)tctx > (uintptr_t)1U); | ||
| 156 | |||
| 157 | szind_t szind = sz_size2index(usize); | ||
| 158 | malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 159 | |||
| 160 | assert(tctx->cnts.curobjs > 0); | ||
| 161 | assert(tctx->cnts.curbytes >= usize); | ||
| 162 | /* | ||
| 163 | * It's not correct to do equivalent asserts for unbiased bytes, because | ||
| 164 | * of the potential for races with prof.reset calls. The map contents | ||
| 165 | * should really be atomic, but we have not atomic-ified the prof module | ||
| 166 | * yet. | ||
| 167 | */ | ||
| 168 | tctx->cnts.curobjs--; | ||
| 169 | tctx->cnts.curobjs_shifted_unbiased -= prof_shifted_unbiased_cnt[szind]; | ||
| 170 | tctx->cnts.curbytes -= usize; | ||
| 171 | tctx->cnts.curbytes_unbiased -= prof_unbiased_sz[szind]; | ||
| 172 | |||
| 173 | prof_try_log(tsd, usize, prof_info); | ||
| 174 | |||
| 175 | prof_tctx_try_destroy(tsd, tctx); | ||
| 176 | |||
| 177 | if (opt_prof_stats) { | ||
| 178 | prof_stats_dec(tsd, szind, prof_info->alloc_size); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | prof_tctx_t * | ||
| 183 | prof_tctx_create(tsd_t *tsd) { | ||
| 184 | if (!tsd_nominal(tsd) || tsd_reentrancy_level_get(tsd) > 0) { | ||
| 185 | return NULL; | ||
| 186 | } | ||
| 187 | |||
| 188 | prof_tdata_t *tdata = prof_tdata_get(tsd, true); | ||
| 189 | if (tdata == NULL) { | ||
| 190 | return NULL; | ||
| 191 | } | ||
| 192 | |||
| 193 | prof_bt_t bt; | ||
| 194 | bt_init(&bt, tdata->vec); | ||
| 195 | prof_backtrace(tsd, &bt); | ||
| 196 | return prof_lookup(tsd, &bt); | ||
| 197 | } | ||
| 198 | |||
| 199 | /* | ||
| 200 | * The bodies of this function and prof_leakcheck() are compiled out unless heap | ||
| 201 | * profiling is enabled, so that it is possible to compile jemalloc with | ||
| 202 | * floating point support completely disabled. Avoiding floating point code is | ||
| 203 | * important on memory-constrained systems, but it also enables a workaround for | ||
| 204 | * versions of glibc that don't properly save/restore floating point registers | ||
| 205 | * during dynamic lazy symbol loading (which internally calls into whatever | ||
| 206 | * malloc implementation happens to be integrated into the application). Note | ||
| 207 | * that some compilers (e.g. gcc 4.8) may use floating point registers for fast | ||
| 208 | * memory moves, so jemalloc must be compiled with such optimizations disabled | ||
| 209 | * (e.g. | ||
| 210 | * -mno-sse) in order for the workaround to be complete. | ||
| 211 | */ | ||
| 212 | uint64_t | ||
| 213 | prof_sample_new_event_wait(tsd_t *tsd) { | ||
| 214 | #ifdef JEMALLOC_PROF | ||
| 215 | if (lg_prof_sample == 0) { | ||
| 216 | return TE_MIN_START_WAIT; | ||
| 217 | } | ||
| 218 | |||
| 219 | /* | ||
| 220 | * Compute sample interval as a geometrically distributed random | ||
| 221 | * variable with mean (2^lg_prof_sample). | ||
| 222 | * | ||
| 223 | * __ __ | ||
| 224 | * | log(u) | 1 | ||
| 225 | * bytes_until_sample = | -------- |, where p = --------------- | ||
| 226 | * | log(1-p) | lg_prof_sample | ||
| 227 | * 2 | ||
| 228 | * | ||
| 229 | * For more information on the math, see: | ||
| 230 | * | ||
| 231 | * Non-Uniform Random Variate Generation | ||
| 232 | * Luc Devroye | ||
| 233 | * Springer-Verlag, New York, 1986 | ||
| 234 | * pp 500 | ||
| 235 | * (http://luc.devroye.org/rnbookindex.html) | ||
| 236 | * | ||
| 237 | * In the actual computation, there's a non-zero probability that our | ||
| 238 | * pseudo random number generator generates an exact 0, and to avoid | ||
| 239 | * log(0), we set u to 1.0 in case r is 0. Therefore u effectively is | ||
| 240 | * uniformly distributed in (0, 1] instead of [0, 1). Further, rather | ||
| 241 | * than taking the ceiling, we take the floor and then add 1, since | ||
| 242 | * otherwise bytes_until_sample would be 0 if u is exactly 1.0. | ||
| 243 | */ | ||
| 244 | uint64_t r = prng_lg_range_u64(tsd_prng_statep_get(tsd), 53); | ||
| 245 | double u = (r == 0U) ? 1.0 : (double)r * (1.0/9007199254740992.0L); | ||
| 246 | return (uint64_t)(log(u) / | ||
| 247 | log(1.0 - (1.0 / (double)((uint64_t)1U << lg_prof_sample)))) | ||
| 248 | + (uint64_t)1U; | ||
| 249 | #else | ||
| 250 | not_reached(); | ||
| 251 | return TE_MAX_START_WAIT; | ||
| 252 | #endif | ||
| 253 | } | ||
| 254 | |||
| 255 | uint64_t | ||
| 256 | prof_sample_postponed_event_wait(tsd_t *tsd) { | ||
| 257 | /* | ||
| 258 | * The postponed wait time for prof sample event is computed as if we | ||
| 259 | * want a new wait time (i.e. as if the event were triggered). If we | ||
| 260 | * instead postpone to the immediate next allocation, like how we're | ||
| 261 | * handling the other events, then we can have sampling bias, if e.g. | ||
| 262 | * the allocation immediately following a reentrancy always comes from | ||
| 263 | * the same stack trace. | ||
| 264 | */ | ||
| 265 | return prof_sample_new_event_wait(tsd); | ||
| 266 | } | ||
| 267 | |||
| 268 | void | ||
| 269 | prof_sample_event_handler(tsd_t *tsd, uint64_t elapsed) { | ||
| 270 | cassert(config_prof); | ||
| 271 | assert(elapsed > 0 && elapsed != TE_INVALID_ELAPSED); | ||
| 272 | if (prof_interval == 0 || !prof_active_get_unlocked()) { | ||
| 273 | return; | ||
| 274 | } | ||
| 275 | if (counter_accum(tsd_tsdn(tsd), &prof_idump_accumulated, elapsed)) { | ||
| 276 | prof_idump(tsd_tsdn(tsd)); | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | static void | ||
| 281 | prof_fdump(void) { | ||
| 282 | tsd_t *tsd; | ||
| 283 | |||
| 284 | cassert(config_prof); | ||
| 285 | assert(opt_prof_final); | ||
| 286 | |||
| 287 | if (!prof_booted) { | ||
| 288 | return; | ||
| 289 | } | ||
| 290 | tsd = tsd_fetch(); | ||
| 291 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 292 | |||
| 293 | prof_fdump_impl(tsd); | ||
| 294 | } | ||
| 295 | |||
| 296 | static bool | ||
| 297 | prof_idump_accum_init(void) { | ||
| 298 | cassert(config_prof); | ||
| 299 | |||
| 300 | return counter_accum_init(&prof_idump_accumulated, prof_interval); | ||
| 301 | } | ||
| 302 | |||
| 303 | void | ||
| 304 | prof_idump(tsdn_t *tsdn) { | ||
| 305 | tsd_t *tsd; | ||
| 306 | prof_tdata_t *tdata; | ||
| 307 | |||
| 308 | cassert(config_prof); | ||
| 309 | |||
| 310 | if (!prof_booted || tsdn_null(tsdn) || !prof_active_get_unlocked()) { | ||
| 311 | return; | ||
| 312 | } | ||
| 313 | tsd = tsdn_tsd(tsdn); | ||
| 314 | if (tsd_reentrancy_level_get(tsd) > 0) { | ||
| 315 | return; | ||
| 316 | } | ||
| 317 | |||
| 318 | tdata = prof_tdata_get(tsd, true); | ||
| 319 | if (tdata == NULL) { | ||
| 320 | return; | ||
| 321 | } | ||
| 322 | if (tdata->enq) { | ||
| 323 | tdata->enq_idump = true; | ||
| 324 | return; | ||
| 325 | } | ||
| 326 | |||
| 327 | prof_idump_impl(tsd); | ||
| 328 | } | ||
| 329 | |||
| 330 | bool | ||
| 331 | prof_mdump(tsd_t *tsd, const char *filename) { | ||
| 332 | cassert(config_prof); | ||
| 333 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 334 | |||
| 335 | if (!opt_prof || !prof_booted) { | ||
| 336 | return true; | ||
| 337 | } | ||
| 338 | |||
| 339 | return prof_mdump_impl(tsd, filename); | ||
| 340 | } | ||
| 341 | |||
| 342 | void | ||
| 343 | prof_gdump(tsdn_t *tsdn) { | ||
| 344 | tsd_t *tsd; | ||
| 345 | prof_tdata_t *tdata; | ||
| 346 | |||
| 347 | cassert(config_prof); | ||
| 348 | |||
| 349 | if (!prof_booted || tsdn_null(tsdn) || !prof_active_get_unlocked()) { | ||
| 350 | return; | ||
| 351 | } | ||
| 352 | tsd = tsdn_tsd(tsdn); | ||
| 353 | if (tsd_reentrancy_level_get(tsd) > 0) { | ||
| 354 | return; | ||
| 355 | } | ||
| 356 | |||
| 357 | tdata = prof_tdata_get(tsd, false); | ||
| 358 | if (tdata == NULL) { | ||
| 359 | return; | ||
| 360 | } | ||
| 361 | if (tdata->enq) { | ||
| 362 | tdata->enq_gdump = true; | ||
| 363 | return; | ||
| 364 | } | ||
| 365 | |||
| 366 | prof_gdump_impl(tsd); | ||
| 367 | } | ||
| 368 | |||
| 369 | static uint64_t | ||
| 370 | prof_thr_uid_alloc(tsdn_t *tsdn) { | ||
| 371 | uint64_t thr_uid; | ||
| 372 | |||
| 373 | malloc_mutex_lock(tsdn, &next_thr_uid_mtx); | ||
| 374 | thr_uid = next_thr_uid; | ||
| 375 | next_thr_uid++; | ||
| 376 | malloc_mutex_unlock(tsdn, &next_thr_uid_mtx); | ||
| 377 | |||
| 378 | return thr_uid; | ||
| 379 | } | ||
| 380 | |||
| 381 | prof_tdata_t * | ||
| 382 | prof_tdata_init(tsd_t *tsd) { | ||
| 383 | return prof_tdata_init_impl(tsd, prof_thr_uid_alloc(tsd_tsdn(tsd)), 0, | ||
| 384 | NULL, prof_thread_active_init_get(tsd_tsdn(tsd))); | ||
| 385 | } | ||
| 386 | |||
| 387 | prof_tdata_t * | ||
| 388 | prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata) { | ||
| 389 | uint64_t thr_uid = tdata->thr_uid; | ||
| 390 | uint64_t thr_discrim = tdata->thr_discrim + 1; | ||
| 391 | char *thread_name = (tdata->thread_name != NULL) ? | ||
| 392 | prof_thread_name_alloc(tsd, tdata->thread_name) : NULL; | ||
| 393 | bool active = tdata->active; | ||
| 394 | |||
| 395 | prof_tdata_detach(tsd, tdata); | ||
| 396 | return prof_tdata_init_impl(tsd, thr_uid, thr_discrim, thread_name, | ||
| 397 | active); | ||
| 398 | } | ||
| 399 | |||
| 400 | void | ||
| 401 | prof_tdata_cleanup(tsd_t *tsd) { | ||
| 402 | prof_tdata_t *tdata; | ||
| 403 | |||
| 404 | if (!config_prof) { | ||
| 405 | return; | ||
| 406 | } | ||
| 407 | |||
| 408 | tdata = tsd_prof_tdata_get(tsd); | ||
| 409 | if (tdata != NULL) { | ||
| 410 | prof_tdata_detach(tsd, tdata); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 414 | bool | ||
| 415 | prof_active_get(tsdn_t *tsdn) { | ||
| 416 | bool prof_active_current; | ||
| 417 | |||
| 418 | prof_active_assert(); | ||
| 419 | malloc_mutex_lock(tsdn, &prof_active_mtx); | ||
| 420 | prof_active_current = prof_active_state; | ||
| 421 | malloc_mutex_unlock(tsdn, &prof_active_mtx); | ||
| 422 | return prof_active_current; | ||
| 423 | } | ||
| 424 | |||
| 425 | bool | ||
| 426 | prof_active_set(tsdn_t *tsdn, bool active) { | ||
| 427 | bool prof_active_old; | ||
| 428 | |||
| 429 | prof_active_assert(); | ||
| 430 | malloc_mutex_lock(tsdn, &prof_active_mtx); | ||
| 431 | prof_active_old = prof_active_state; | ||
| 432 | prof_active_state = active; | ||
| 433 | malloc_mutex_unlock(tsdn, &prof_active_mtx); | ||
| 434 | prof_active_assert(); | ||
| 435 | return prof_active_old; | ||
| 436 | } | ||
| 437 | |||
| 438 | const char * | ||
| 439 | prof_thread_name_get(tsd_t *tsd) { | ||
| 440 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 441 | |||
| 442 | prof_tdata_t *tdata; | ||
| 443 | |||
| 444 | tdata = prof_tdata_get(tsd, true); | ||
| 445 | if (tdata == NULL) { | ||
| 446 | return ""; | ||
| 447 | } | ||
| 448 | return (tdata->thread_name != NULL ? tdata->thread_name : ""); | ||
| 449 | } | ||
| 450 | |||
| 451 | int | ||
| 452 | prof_thread_name_set(tsd_t *tsd, const char *thread_name) { | ||
| 453 | if (opt_prof_sys_thread_name) { | ||
| 454 | return ENOENT; | ||
| 455 | } else { | ||
| 456 | return prof_thread_name_set_impl(tsd, thread_name); | ||
| 457 | } | ||
| 458 | } | ||
| 459 | |||
| 460 | bool | ||
| 461 | prof_thread_active_get(tsd_t *tsd) { | ||
| 462 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 463 | |||
| 464 | prof_tdata_t *tdata; | ||
| 465 | |||
| 466 | tdata = prof_tdata_get(tsd, true); | ||
| 467 | if (tdata == NULL) { | ||
| 468 | return false; | ||
| 469 | } | ||
| 470 | return tdata->active; | ||
| 471 | } | ||
| 472 | |||
| 473 | bool | ||
| 474 | prof_thread_active_set(tsd_t *tsd, bool active) { | ||
| 475 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 476 | |||
| 477 | prof_tdata_t *tdata; | ||
| 478 | |||
| 479 | tdata = prof_tdata_get(tsd, true); | ||
| 480 | if (tdata == NULL) { | ||
| 481 | return true; | ||
| 482 | } | ||
| 483 | tdata->active = active; | ||
| 484 | return false; | ||
| 485 | } | ||
| 486 | |||
| 487 | bool | ||
| 488 | prof_thread_active_init_get(tsdn_t *tsdn) { | ||
| 489 | bool active_init; | ||
| 490 | |||
| 491 | malloc_mutex_lock(tsdn, &prof_thread_active_init_mtx); | ||
| 492 | active_init = prof_thread_active_init; | ||
| 493 | malloc_mutex_unlock(tsdn, &prof_thread_active_init_mtx); | ||
| 494 | return active_init; | ||
| 495 | } | ||
| 496 | |||
| 497 | bool | ||
| 498 | prof_thread_active_init_set(tsdn_t *tsdn, bool active_init) { | ||
| 499 | bool active_init_old; | ||
| 500 | |||
| 501 | malloc_mutex_lock(tsdn, &prof_thread_active_init_mtx); | ||
| 502 | active_init_old = prof_thread_active_init; | ||
| 503 | prof_thread_active_init = active_init; | ||
| 504 | malloc_mutex_unlock(tsdn, &prof_thread_active_init_mtx); | ||
| 505 | return active_init_old; | ||
| 506 | } | ||
| 507 | |||
| 508 | bool | ||
| 509 | prof_gdump_get(tsdn_t *tsdn) { | ||
| 510 | bool prof_gdump_current; | ||
| 511 | |||
| 512 | malloc_mutex_lock(tsdn, &prof_gdump_mtx); | ||
| 513 | prof_gdump_current = prof_gdump_val; | ||
| 514 | malloc_mutex_unlock(tsdn, &prof_gdump_mtx); | ||
| 515 | return prof_gdump_current; | ||
| 516 | } | ||
| 517 | |||
| 518 | bool | ||
| 519 | prof_gdump_set(tsdn_t *tsdn, bool gdump) { | ||
| 520 | bool prof_gdump_old; | ||
| 521 | |||
| 522 | malloc_mutex_lock(tsdn, &prof_gdump_mtx); | ||
| 523 | prof_gdump_old = prof_gdump_val; | ||
| 524 | prof_gdump_val = gdump; | ||
| 525 | malloc_mutex_unlock(tsdn, &prof_gdump_mtx); | ||
| 526 | return prof_gdump_old; | ||
| 527 | } | ||
| 528 | |||
| 529 | void | ||
| 530 | prof_backtrace_hook_set(prof_backtrace_hook_t hook) { | ||
| 531 | atomic_store_p(&prof_backtrace_hook, hook, ATOMIC_RELEASE); | ||
| 532 | } | ||
| 533 | |||
| 534 | prof_backtrace_hook_t | ||
| 535 | prof_backtrace_hook_get() { | ||
| 536 | return (prof_backtrace_hook_t)atomic_load_p(&prof_backtrace_hook, | ||
| 537 | ATOMIC_ACQUIRE); | ||
| 538 | } | ||
| 539 | |||
| 540 | void | ||
| 541 | prof_dump_hook_set(prof_dump_hook_t hook) { | ||
| 542 | atomic_store_p(&prof_dump_hook, hook, ATOMIC_RELEASE); | ||
| 543 | } | ||
| 544 | |||
| 545 | prof_dump_hook_t | ||
| 546 | prof_dump_hook_get() { | ||
| 547 | return (prof_dump_hook_t)atomic_load_p(&prof_dump_hook, | ||
| 548 | ATOMIC_ACQUIRE); | ||
| 549 | } | ||
| 550 | |||
| 551 | void | ||
| 552 | prof_boot0(void) { | ||
| 553 | cassert(config_prof); | ||
| 554 | |||
| 555 | memcpy(opt_prof_prefix, PROF_PREFIX_DEFAULT, | ||
| 556 | sizeof(PROF_PREFIX_DEFAULT)); | ||
| 557 | } | ||
| 558 | |||
| 559 | void | ||
| 560 | prof_boot1(void) { | ||
| 561 | cassert(config_prof); | ||
| 562 | |||
| 563 | /* | ||
| 564 | * opt_prof must be in its final state before any arenas are | ||
| 565 | * initialized, so this function must be executed early. | ||
| 566 | */ | ||
| 567 | if (opt_prof_leak_error && !opt_prof_leak) { | ||
| 568 | opt_prof_leak = true; | ||
| 569 | } | ||
| 570 | |||
| 571 | if (opt_prof_leak && !opt_prof) { | ||
| 572 | /* | ||
| 573 | * Enable opt_prof, but in such a way that profiles are never | ||
| 574 | * automatically dumped. | ||
| 575 | */ | ||
| 576 | opt_prof = true; | ||
| 577 | opt_prof_gdump = false; | ||
| 578 | } else if (opt_prof) { | ||
| 579 | if (opt_lg_prof_interval >= 0) { | ||
| 580 | prof_interval = (((uint64_t)1U) << | ||
| 581 | opt_lg_prof_interval); | ||
| 582 | } | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | bool | ||
| 587 | prof_boot2(tsd_t *tsd, base_t *base) { | ||
| 588 | cassert(config_prof); | ||
| 589 | |||
| 590 | /* | ||
| 591 | * Initialize the global mutexes unconditionally to maintain correct | ||
| 592 | * stats when opt_prof is false. | ||
| 593 | */ | ||
| 594 | if (malloc_mutex_init(&prof_active_mtx, "prof_active", | ||
| 595 | WITNESS_RANK_PROF_ACTIVE, malloc_mutex_rank_exclusive)) { | ||
| 596 | return true; | ||
| 597 | } | ||
| 598 | if (malloc_mutex_init(&prof_gdump_mtx, "prof_gdump", | ||
| 599 | WITNESS_RANK_PROF_GDUMP, malloc_mutex_rank_exclusive)) { | ||
| 600 | return true; | ||
| 601 | } | ||
| 602 | if (malloc_mutex_init(&prof_thread_active_init_mtx, | ||
| 603 | "prof_thread_active_init", WITNESS_RANK_PROF_THREAD_ACTIVE_INIT, | ||
| 604 | malloc_mutex_rank_exclusive)) { | ||
| 605 | return true; | ||
| 606 | } | ||
| 607 | if (malloc_mutex_init(&bt2gctx_mtx, "prof_bt2gctx", | ||
| 608 | WITNESS_RANK_PROF_BT2GCTX, malloc_mutex_rank_exclusive)) { | ||
| 609 | return true; | ||
| 610 | } | ||
| 611 | if (malloc_mutex_init(&tdatas_mtx, "prof_tdatas", | ||
| 612 | WITNESS_RANK_PROF_TDATAS, malloc_mutex_rank_exclusive)) { | ||
| 613 | return true; | ||
| 614 | } | ||
| 615 | if (malloc_mutex_init(&next_thr_uid_mtx, "prof_next_thr_uid", | ||
| 616 | WITNESS_RANK_PROF_NEXT_THR_UID, malloc_mutex_rank_exclusive)) { | ||
| 617 | return true; | ||
| 618 | } | ||
| 619 | if (malloc_mutex_init(&prof_stats_mtx, "prof_stats", | ||
| 620 | WITNESS_RANK_PROF_STATS, malloc_mutex_rank_exclusive)) { | ||
| 621 | return true; | ||
| 622 | } | ||
| 623 | if (malloc_mutex_init(&prof_dump_filename_mtx, | ||
| 624 | "prof_dump_filename", WITNESS_RANK_PROF_DUMP_FILENAME, | ||
| 625 | malloc_mutex_rank_exclusive)) { | ||
| 626 | return true; | ||
| 627 | } | ||
| 628 | if (malloc_mutex_init(&prof_dump_mtx, "prof_dump", | ||
| 629 | WITNESS_RANK_PROF_DUMP, malloc_mutex_rank_exclusive)) { | ||
| 630 | return true; | ||
| 631 | } | ||
| 632 | |||
| 633 | if (opt_prof) { | ||
| 634 | lg_prof_sample = opt_lg_prof_sample; | ||
| 635 | prof_unbias_map_init(); | ||
| 636 | prof_active_state = opt_prof_active; | ||
| 637 | prof_gdump_val = opt_prof_gdump; | ||
| 638 | prof_thread_active_init = opt_prof_thread_active_init; | ||
| 639 | |||
| 640 | if (prof_data_init(tsd)) { | ||
| 641 | return true; | ||
| 642 | } | ||
| 643 | |||
| 644 | next_thr_uid = 0; | ||
| 645 | if (prof_idump_accum_init()) { | ||
| 646 | return true; | ||
| 647 | } | ||
| 648 | |||
| 649 | if (opt_prof_final && opt_prof_prefix[0] != '\0' && | ||
| 650 | atexit(prof_fdump) != 0) { | ||
| 651 | malloc_write("<jemalloc>: Error in atexit()\n"); | ||
| 652 | if (opt_abort) { | ||
| 653 | abort(); | ||
| 654 | } | ||
| 655 | } | ||
| 656 | |||
| 657 | if (prof_log_init(tsd)) { | ||
| 658 | return true; | ||
| 659 | } | ||
| 660 | |||
| 661 | if (prof_recent_init()) { | ||
| 662 | return true; | ||
| 663 | } | ||
| 664 | |||
| 665 | prof_base = base; | ||
| 666 | |||
| 667 | gctx_locks = (malloc_mutex_t *)base_alloc(tsd_tsdn(tsd), base, | ||
| 668 | PROF_NCTX_LOCKS * sizeof(malloc_mutex_t), CACHELINE); | ||
| 669 | if (gctx_locks == NULL) { | ||
| 670 | return true; | ||
| 671 | } | ||
| 672 | for (unsigned i = 0; i < PROF_NCTX_LOCKS; i++) { | ||
| 673 | if (malloc_mutex_init(&gctx_locks[i], "prof_gctx", | ||
| 674 | WITNESS_RANK_PROF_GCTX, | ||
| 675 | malloc_mutex_rank_exclusive)) { | ||
| 676 | return true; | ||
| 677 | } | ||
| 678 | } | ||
| 679 | |||
| 680 | tdata_locks = (malloc_mutex_t *)base_alloc(tsd_tsdn(tsd), base, | ||
| 681 | PROF_NTDATA_LOCKS * sizeof(malloc_mutex_t), CACHELINE); | ||
| 682 | if (tdata_locks == NULL) { | ||
| 683 | return true; | ||
| 684 | } | ||
| 685 | for (unsigned i = 0; i < PROF_NTDATA_LOCKS; i++) { | ||
| 686 | if (malloc_mutex_init(&tdata_locks[i], "prof_tdata", | ||
| 687 | WITNESS_RANK_PROF_TDATA, | ||
| 688 | malloc_mutex_rank_exclusive)) { | ||
| 689 | return true; | ||
| 690 | } | ||
| 691 | } | ||
| 692 | |||
| 693 | prof_unwind_init(); | ||
| 694 | prof_hooks_init(); | ||
| 695 | } | ||
| 696 | prof_booted = true; | ||
| 697 | |||
| 698 | return false; | ||
| 699 | } | ||
| 700 | |||
| 701 | void | ||
| 702 | prof_prefork0(tsdn_t *tsdn) { | ||
| 703 | if (config_prof && opt_prof) { | ||
| 704 | unsigned i; | ||
| 705 | |||
| 706 | malloc_mutex_prefork(tsdn, &prof_dump_mtx); | ||
| 707 | malloc_mutex_prefork(tsdn, &bt2gctx_mtx); | ||
| 708 | malloc_mutex_prefork(tsdn, &tdatas_mtx); | ||
| 709 | for (i = 0; i < PROF_NTDATA_LOCKS; i++) { | ||
| 710 | malloc_mutex_prefork(tsdn, &tdata_locks[i]); | ||
| 711 | } | ||
| 712 | malloc_mutex_prefork(tsdn, &log_mtx); | ||
| 713 | for (i = 0; i < PROF_NCTX_LOCKS; i++) { | ||
| 714 | malloc_mutex_prefork(tsdn, &gctx_locks[i]); | ||
| 715 | } | ||
| 716 | malloc_mutex_prefork(tsdn, &prof_recent_dump_mtx); | ||
| 717 | } | ||
| 718 | } | ||
| 719 | |||
| 720 | void | ||
| 721 | prof_prefork1(tsdn_t *tsdn) { | ||
| 722 | if (config_prof && opt_prof) { | ||
| 723 | counter_prefork(tsdn, &prof_idump_accumulated); | ||
| 724 | malloc_mutex_prefork(tsdn, &prof_active_mtx); | ||
| 725 | malloc_mutex_prefork(tsdn, &prof_dump_filename_mtx); | ||
| 726 | malloc_mutex_prefork(tsdn, &prof_gdump_mtx); | ||
| 727 | malloc_mutex_prefork(tsdn, &prof_recent_alloc_mtx); | ||
| 728 | malloc_mutex_prefork(tsdn, &prof_stats_mtx); | ||
| 729 | malloc_mutex_prefork(tsdn, &next_thr_uid_mtx); | ||
| 730 | malloc_mutex_prefork(tsdn, &prof_thread_active_init_mtx); | ||
| 731 | } | ||
| 732 | } | ||
| 733 | |||
| 734 | void | ||
| 735 | prof_postfork_parent(tsdn_t *tsdn) { | ||
| 736 | if (config_prof && opt_prof) { | ||
| 737 | unsigned i; | ||
| 738 | |||
| 739 | malloc_mutex_postfork_parent(tsdn, | ||
| 740 | &prof_thread_active_init_mtx); | ||
| 741 | malloc_mutex_postfork_parent(tsdn, &next_thr_uid_mtx); | ||
| 742 | malloc_mutex_postfork_parent(tsdn, &prof_stats_mtx); | ||
| 743 | malloc_mutex_postfork_parent(tsdn, &prof_recent_alloc_mtx); | ||
| 744 | malloc_mutex_postfork_parent(tsdn, &prof_gdump_mtx); | ||
| 745 | malloc_mutex_postfork_parent(tsdn, &prof_dump_filename_mtx); | ||
| 746 | malloc_mutex_postfork_parent(tsdn, &prof_active_mtx); | ||
| 747 | counter_postfork_parent(tsdn, &prof_idump_accumulated); | ||
| 748 | malloc_mutex_postfork_parent(tsdn, &prof_recent_dump_mtx); | ||
| 749 | for (i = 0; i < PROF_NCTX_LOCKS; i++) { | ||
| 750 | malloc_mutex_postfork_parent(tsdn, &gctx_locks[i]); | ||
| 751 | } | ||
| 752 | malloc_mutex_postfork_parent(tsdn, &log_mtx); | ||
| 753 | for (i = 0; i < PROF_NTDATA_LOCKS; i++) { | ||
| 754 | malloc_mutex_postfork_parent(tsdn, &tdata_locks[i]); | ||
| 755 | } | ||
| 756 | malloc_mutex_postfork_parent(tsdn, &tdatas_mtx); | ||
| 757 | malloc_mutex_postfork_parent(tsdn, &bt2gctx_mtx); | ||
| 758 | malloc_mutex_postfork_parent(tsdn, &prof_dump_mtx); | ||
| 759 | } | ||
| 760 | } | ||
| 761 | |||
| 762 | void | ||
| 763 | prof_postfork_child(tsdn_t *tsdn) { | ||
| 764 | if (config_prof && opt_prof) { | ||
| 765 | unsigned i; | ||
| 766 | |||
| 767 | malloc_mutex_postfork_child(tsdn, &prof_thread_active_init_mtx); | ||
| 768 | malloc_mutex_postfork_child(tsdn, &next_thr_uid_mtx); | ||
| 769 | malloc_mutex_postfork_child(tsdn, &prof_stats_mtx); | ||
| 770 | malloc_mutex_postfork_child(tsdn, &prof_recent_alloc_mtx); | ||
| 771 | malloc_mutex_postfork_child(tsdn, &prof_gdump_mtx); | ||
| 772 | malloc_mutex_postfork_child(tsdn, &prof_dump_filename_mtx); | ||
| 773 | malloc_mutex_postfork_child(tsdn, &prof_active_mtx); | ||
| 774 | counter_postfork_child(tsdn, &prof_idump_accumulated); | ||
| 775 | malloc_mutex_postfork_child(tsdn, &prof_recent_dump_mtx); | ||
| 776 | for (i = 0; i < PROF_NCTX_LOCKS; i++) { | ||
| 777 | malloc_mutex_postfork_child(tsdn, &gctx_locks[i]); | ||
| 778 | } | ||
| 779 | malloc_mutex_postfork_child(tsdn, &log_mtx); | ||
| 780 | for (i = 0; i < PROF_NTDATA_LOCKS; i++) { | ||
| 781 | malloc_mutex_postfork_child(tsdn, &tdata_locks[i]); | ||
| 782 | } | ||
| 783 | malloc_mutex_postfork_child(tsdn, &tdatas_mtx); | ||
| 784 | malloc_mutex_postfork_child(tsdn, &bt2gctx_mtx); | ||
| 785 | malloc_mutex_postfork_child(tsdn, &prof_dump_mtx); | ||
| 786 | } | ||
| 787 | } | ||
| 788 | |||
| 789 | /******************************************************************************/ | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/prof_data.c b/examples/redis-unstable/deps/jemalloc/src/prof_data.c deleted file mode 100644 index bfa55be..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/prof_data.c +++ /dev/null | |||
| @@ -1,1447 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/ckh.h" | ||
| 6 | #include "jemalloc/internal/hash.h" | ||
| 7 | #include "jemalloc/internal/malloc_io.h" | ||
| 8 | #include "jemalloc/internal/prof_data.h" | ||
| 9 | |||
| 10 | /* | ||
| 11 | * This file defines and manages the core profiling data structures. | ||
| 12 | * | ||
| 13 | * Conceptually, profiling data can be imagined as a table with three columns: | ||
| 14 | * thread, stack trace, and current allocation size. (When prof_accum is on, | ||
| 15 | * there's one additional column which is the cumulative allocation size.) | ||
| 16 | * | ||
| 17 | * Implementation wise, each thread maintains a hash recording the stack trace | ||
| 18 | * to allocation size correspondences, which are basically the individual rows | ||
| 19 | * in the table. In addition, two global "indices" are built to make data | ||
| 20 | * aggregation efficient (for dumping): bt2gctx and tdatas, which are basically | ||
| 21 | * the "grouped by stack trace" and "grouped by thread" views of the same table, | ||
| 22 | * respectively. Note that the allocation size is only aggregated to the two | ||
| 23 | * indices at dumping time, so as to optimize for performance. | ||
| 24 | */ | ||
| 25 | |||
| 26 | /******************************************************************************/ | ||
| 27 | |||
| 28 | malloc_mutex_t bt2gctx_mtx; | ||
| 29 | malloc_mutex_t tdatas_mtx; | ||
| 30 | malloc_mutex_t prof_dump_mtx; | ||
| 31 | |||
| 32 | /* | ||
| 33 | * Table of mutexes that are shared among gctx's. These are leaf locks, so | ||
| 34 | * there is no problem with using them for more than one gctx at the same time. | ||
| 35 | * The primary motivation for this sharing though is that gctx's are ephemeral, | ||
| 36 | * and destroying mutexes causes complications for systems that allocate when | ||
| 37 | * creating/destroying mutexes. | ||
| 38 | */ | ||
| 39 | malloc_mutex_t *gctx_locks; | ||
| 40 | static atomic_u_t cum_gctxs; /* Atomic counter. */ | ||
| 41 | |||
| 42 | /* | ||
| 43 | * Table of mutexes that are shared among tdata's. No operations require | ||
| 44 | * holding multiple tdata locks, so there is no problem with using them for more | ||
| 45 | * than one tdata at the same time, even though a gctx lock may be acquired | ||
| 46 | * while holding a tdata lock. | ||
| 47 | */ | ||
| 48 | malloc_mutex_t *tdata_locks; | ||
| 49 | |||
| 50 | /* | ||
| 51 | * Global hash of (prof_bt_t *)-->(prof_gctx_t *). This is the master data | ||
| 52 | * structure that knows about all backtraces currently captured. | ||
| 53 | */ | ||
| 54 | static ckh_t bt2gctx; | ||
| 55 | |||
| 56 | /* | ||
| 57 | * Tree of all extant prof_tdata_t structures, regardless of state, | ||
| 58 | * {attached,detached,expired}. | ||
| 59 | */ | ||
| 60 | static prof_tdata_tree_t tdatas; | ||
| 61 | |||
| 62 | size_t prof_unbiased_sz[PROF_SC_NSIZES]; | ||
| 63 | size_t prof_shifted_unbiased_cnt[PROF_SC_NSIZES]; | ||
| 64 | |||
| 65 | /******************************************************************************/ | ||
| 66 | /* Red-black trees. */ | ||
| 67 | |||
| 68 | static int | ||
| 69 | prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b) { | ||
| 70 | uint64_t a_thr_uid = a->thr_uid; | ||
| 71 | uint64_t b_thr_uid = b->thr_uid; | ||
| 72 | int ret = (a_thr_uid > b_thr_uid) - (a_thr_uid < b_thr_uid); | ||
| 73 | if (ret == 0) { | ||
| 74 | uint64_t a_thr_discrim = a->thr_discrim; | ||
| 75 | uint64_t b_thr_discrim = b->thr_discrim; | ||
| 76 | ret = (a_thr_discrim > b_thr_discrim) - (a_thr_discrim < | ||
| 77 | b_thr_discrim); | ||
| 78 | if (ret == 0) { | ||
| 79 | uint64_t a_tctx_uid = a->tctx_uid; | ||
| 80 | uint64_t b_tctx_uid = b->tctx_uid; | ||
| 81 | ret = (a_tctx_uid > b_tctx_uid) - (a_tctx_uid < | ||
| 82 | b_tctx_uid); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | return ret; | ||
| 86 | } | ||
| 87 | |||
| 88 | rb_gen(static UNUSED, tctx_tree_, prof_tctx_tree_t, prof_tctx_t, | ||
| 89 | tctx_link, prof_tctx_comp) | ||
| 90 | |||
| 91 | static int | ||
| 92 | prof_gctx_comp(const prof_gctx_t *a, const prof_gctx_t *b) { | ||
| 93 | unsigned a_len = a->bt.len; | ||
| 94 | unsigned b_len = b->bt.len; | ||
| 95 | unsigned comp_len = (a_len < b_len) ? a_len : b_len; | ||
| 96 | int ret = memcmp(a->bt.vec, b->bt.vec, comp_len * sizeof(void *)); | ||
| 97 | if (ret == 0) { | ||
| 98 | ret = (a_len > b_len) - (a_len < b_len); | ||
| 99 | } | ||
| 100 | return ret; | ||
| 101 | } | ||
| 102 | |||
| 103 | rb_gen(static UNUSED, gctx_tree_, prof_gctx_tree_t, prof_gctx_t, dump_link, | ||
| 104 | prof_gctx_comp) | ||
| 105 | |||
| 106 | static int | ||
| 107 | prof_tdata_comp(const prof_tdata_t *a, const prof_tdata_t *b) { | ||
| 108 | int ret; | ||
| 109 | uint64_t a_uid = a->thr_uid; | ||
| 110 | uint64_t b_uid = b->thr_uid; | ||
| 111 | |||
| 112 | ret = ((a_uid > b_uid) - (a_uid < b_uid)); | ||
| 113 | if (ret == 0) { | ||
| 114 | uint64_t a_discrim = a->thr_discrim; | ||
| 115 | uint64_t b_discrim = b->thr_discrim; | ||
| 116 | |||
| 117 | ret = ((a_discrim > b_discrim) - (a_discrim < b_discrim)); | ||
| 118 | } | ||
| 119 | return ret; | ||
| 120 | } | ||
| 121 | |||
| 122 | rb_gen(static UNUSED, tdata_tree_, prof_tdata_tree_t, prof_tdata_t, tdata_link, | ||
| 123 | prof_tdata_comp) | ||
| 124 | |||
| 125 | /******************************************************************************/ | ||
| 126 | |||
| 127 | static malloc_mutex_t * | ||
| 128 | prof_gctx_mutex_choose(void) { | ||
| 129 | unsigned ngctxs = atomic_fetch_add_u(&cum_gctxs, 1, ATOMIC_RELAXED); | ||
| 130 | |||
| 131 | return &gctx_locks[(ngctxs - 1) % PROF_NCTX_LOCKS]; | ||
| 132 | } | ||
| 133 | |||
| 134 | static malloc_mutex_t * | ||
| 135 | prof_tdata_mutex_choose(uint64_t thr_uid) { | ||
| 136 | return &tdata_locks[thr_uid % PROF_NTDATA_LOCKS]; | ||
| 137 | } | ||
| 138 | |||
| 139 | bool | ||
| 140 | prof_data_init(tsd_t *tsd) { | ||
| 141 | tdata_tree_new(&tdatas); | ||
| 142 | return ckh_new(tsd, &bt2gctx, PROF_CKH_MINITEMS, | ||
| 143 | prof_bt_hash, prof_bt_keycomp); | ||
| 144 | } | ||
| 145 | |||
| 146 | static void | ||
| 147 | prof_enter(tsd_t *tsd, prof_tdata_t *tdata) { | ||
| 148 | cassert(config_prof); | ||
| 149 | assert(tdata == prof_tdata_get(tsd, false)); | ||
| 150 | |||
| 151 | if (tdata != NULL) { | ||
| 152 | assert(!tdata->enq); | ||
| 153 | tdata->enq = true; | ||
| 154 | } | ||
| 155 | |||
| 156 | malloc_mutex_lock(tsd_tsdn(tsd), &bt2gctx_mtx); | ||
| 157 | } | ||
| 158 | |||
| 159 | static void | ||
| 160 | prof_leave(tsd_t *tsd, prof_tdata_t *tdata) { | ||
| 161 | cassert(config_prof); | ||
| 162 | assert(tdata == prof_tdata_get(tsd, false)); | ||
| 163 | |||
| 164 | malloc_mutex_unlock(tsd_tsdn(tsd), &bt2gctx_mtx); | ||
| 165 | |||
| 166 | if (tdata != NULL) { | ||
| 167 | bool idump, gdump; | ||
| 168 | |||
| 169 | assert(tdata->enq); | ||
| 170 | tdata->enq = false; | ||
| 171 | idump = tdata->enq_idump; | ||
| 172 | tdata->enq_idump = false; | ||
| 173 | gdump = tdata->enq_gdump; | ||
| 174 | tdata->enq_gdump = false; | ||
| 175 | |||
| 176 | if (idump) { | ||
| 177 | prof_idump(tsd_tsdn(tsd)); | ||
| 178 | } | ||
| 179 | if (gdump) { | ||
| 180 | prof_gdump(tsd_tsdn(tsd)); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | static prof_gctx_t * | ||
| 186 | prof_gctx_create(tsdn_t *tsdn, prof_bt_t *bt) { | ||
| 187 | /* | ||
| 188 | * Create a single allocation that has space for vec of length bt->len. | ||
| 189 | */ | ||
| 190 | size_t size = offsetof(prof_gctx_t, vec) + (bt->len * sizeof(void *)); | ||
| 191 | prof_gctx_t *gctx = (prof_gctx_t *)iallocztm(tsdn, size, | ||
| 192 | sz_size2index(size), false, NULL, true, arena_get(TSDN_NULL, 0, true), | ||
| 193 | true); | ||
| 194 | if (gctx == NULL) { | ||
| 195 | return NULL; | ||
| 196 | } | ||
| 197 | gctx->lock = prof_gctx_mutex_choose(); | ||
| 198 | /* | ||
| 199 | * Set nlimbo to 1, in order to avoid a race condition with | ||
| 200 | * prof_tctx_destroy()/prof_gctx_try_destroy(). | ||
| 201 | */ | ||
| 202 | gctx->nlimbo = 1; | ||
| 203 | tctx_tree_new(&gctx->tctxs); | ||
| 204 | /* Duplicate bt. */ | ||
| 205 | memcpy(gctx->vec, bt->vec, bt->len * sizeof(void *)); | ||
| 206 | gctx->bt.vec = gctx->vec; | ||
| 207 | gctx->bt.len = bt->len; | ||
| 208 | return gctx; | ||
| 209 | } | ||
| 210 | |||
| 211 | static void | ||
| 212 | prof_gctx_try_destroy(tsd_t *tsd, prof_tdata_t *tdata_self, | ||
| 213 | prof_gctx_t *gctx) { | ||
| 214 | cassert(config_prof); | ||
| 215 | |||
| 216 | /* | ||
| 217 | * Check that gctx is still unused by any thread cache before destroying | ||
| 218 | * it. prof_lookup() increments gctx->nlimbo in order to avoid a race | ||
| 219 | * condition with this function, as does prof_tctx_destroy() in order to | ||
| 220 | * avoid a race between the main body of prof_tctx_destroy() and entry | ||
| 221 | * into this function. | ||
| 222 | */ | ||
| 223 | prof_enter(tsd, tdata_self); | ||
| 224 | malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); | ||
| 225 | assert(gctx->nlimbo != 0); | ||
| 226 | if (tctx_tree_empty(&gctx->tctxs) && gctx->nlimbo == 1) { | ||
| 227 | /* Remove gctx from bt2gctx. */ | ||
| 228 | if (ckh_remove(tsd, &bt2gctx, &gctx->bt, NULL, NULL)) { | ||
| 229 | not_reached(); | ||
| 230 | } | ||
| 231 | prof_leave(tsd, tdata_self); | ||
| 232 | /* Destroy gctx. */ | ||
| 233 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); | ||
| 234 | idalloctm(tsd_tsdn(tsd), gctx, NULL, NULL, true, true); | ||
| 235 | } else { | ||
| 236 | /* | ||
| 237 | * Compensate for increment in prof_tctx_destroy() or | ||
| 238 | * prof_lookup(). | ||
| 239 | */ | ||
| 240 | gctx->nlimbo--; | ||
| 241 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); | ||
| 242 | prof_leave(tsd, tdata_self); | ||
| 243 | } | ||
| 244 | } | ||
| 245 | |||
| 246 | static bool | ||
| 247 | prof_gctx_should_destroy(prof_gctx_t *gctx) { | ||
| 248 | if (opt_prof_accum) { | ||
| 249 | return false; | ||
| 250 | } | ||
| 251 | if (!tctx_tree_empty(&gctx->tctxs)) { | ||
| 252 | return false; | ||
| 253 | } | ||
| 254 | if (gctx->nlimbo != 0) { | ||
| 255 | return false; | ||
| 256 | } | ||
| 257 | return true; | ||
| 258 | } | ||
| 259 | |||
| 260 | static bool | ||
| 261 | prof_lookup_global(tsd_t *tsd, prof_bt_t *bt, prof_tdata_t *tdata, | ||
| 262 | void **p_btkey, prof_gctx_t **p_gctx, bool *p_new_gctx) { | ||
| 263 | union { | ||
| 264 | prof_gctx_t *p; | ||
| 265 | void *v; | ||
| 266 | } gctx, tgctx; | ||
| 267 | union { | ||
| 268 | prof_bt_t *p; | ||
| 269 | void *v; | ||
| 270 | } btkey; | ||
| 271 | bool new_gctx; | ||
| 272 | |||
| 273 | prof_enter(tsd, tdata); | ||
| 274 | if (ckh_search(&bt2gctx, bt, &btkey.v, &gctx.v)) { | ||
| 275 | /* bt has never been seen before. Insert it. */ | ||
| 276 | prof_leave(tsd, tdata); | ||
| 277 | tgctx.p = prof_gctx_create(tsd_tsdn(tsd), bt); | ||
| 278 | if (tgctx.v == NULL) { | ||
| 279 | return true; | ||
| 280 | } | ||
| 281 | prof_enter(tsd, tdata); | ||
| 282 | if (ckh_search(&bt2gctx, bt, &btkey.v, &gctx.v)) { | ||
| 283 | gctx.p = tgctx.p; | ||
| 284 | btkey.p = &gctx.p->bt; | ||
| 285 | if (ckh_insert(tsd, &bt2gctx, btkey.v, gctx.v)) { | ||
| 286 | /* OOM. */ | ||
| 287 | prof_leave(tsd, tdata); | ||
| 288 | idalloctm(tsd_tsdn(tsd), gctx.v, NULL, NULL, | ||
| 289 | true, true); | ||
| 290 | return true; | ||
| 291 | } | ||
| 292 | new_gctx = true; | ||
| 293 | } else { | ||
| 294 | new_gctx = false; | ||
| 295 | } | ||
| 296 | } else { | ||
| 297 | tgctx.v = NULL; | ||
| 298 | new_gctx = false; | ||
| 299 | } | ||
| 300 | |||
| 301 | if (!new_gctx) { | ||
| 302 | /* | ||
| 303 | * Increment nlimbo, in order to avoid a race condition with | ||
| 304 | * prof_tctx_destroy()/prof_gctx_try_destroy(). | ||
| 305 | */ | ||
| 306 | malloc_mutex_lock(tsd_tsdn(tsd), gctx.p->lock); | ||
| 307 | gctx.p->nlimbo++; | ||
| 308 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx.p->lock); | ||
| 309 | new_gctx = false; | ||
| 310 | |||
| 311 | if (tgctx.v != NULL) { | ||
| 312 | /* Lost race to insert. */ | ||
| 313 | idalloctm(tsd_tsdn(tsd), tgctx.v, NULL, NULL, true, | ||
| 314 | true); | ||
| 315 | } | ||
| 316 | } | ||
| 317 | prof_leave(tsd, tdata); | ||
| 318 | |||
| 319 | *p_btkey = btkey.v; | ||
| 320 | *p_gctx = gctx.p; | ||
| 321 | *p_new_gctx = new_gctx; | ||
| 322 | return false; | ||
| 323 | } | ||
| 324 | |||
| 325 | prof_tctx_t * | ||
| 326 | prof_lookup(tsd_t *tsd, prof_bt_t *bt) { | ||
| 327 | union { | ||
| 328 | prof_tctx_t *p; | ||
| 329 | void *v; | ||
| 330 | } ret; | ||
| 331 | prof_tdata_t *tdata; | ||
| 332 | bool not_found; | ||
| 333 | |||
| 334 | cassert(config_prof); | ||
| 335 | |||
| 336 | tdata = prof_tdata_get(tsd, false); | ||
| 337 | assert(tdata != NULL); | ||
| 338 | |||
| 339 | malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock); | ||
| 340 | not_found = ckh_search(&tdata->bt2tctx, bt, NULL, &ret.v); | ||
| 341 | if (!not_found) { /* Note double negative! */ | ||
| 342 | ret.p->prepared = true; | ||
| 343 | } | ||
| 344 | malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); | ||
| 345 | if (not_found) { | ||
| 346 | void *btkey; | ||
| 347 | prof_gctx_t *gctx; | ||
| 348 | bool new_gctx, error; | ||
| 349 | |||
| 350 | /* | ||
| 351 | * This thread's cache lacks bt. Look for it in the global | ||
| 352 | * cache. | ||
| 353 | */ | ||
| 354 | if (prof_lookup_global(tsd, bt, tdata, &btkey, &gctx, | ||
| 355 | &new_gctx)) { | ||
| 356 | return NULL; | ||
| 357 | } | ||
| 358 | |||
| 359 | /* Link a prof_tctx_t into gctx for this thread. */ | ||
| 360 | ret.v = iallocztm(tsd_tsdn(tsd), sizeof(prof_tctx_t), | ||
| 361 | sz_size2index(sizeof(prof_tctx_t)), false, NULL, true, | ||
| 362 | arena_ichoose(tsd, NULL), true); | ||
| 363 | if (ret.p == NULL) { | ||
| 364 | if (new_gctx) { | ||
| 365 | prof_gctx_try_destroy(tsd, tdata, gctx); | ||
| 366 | } | ||
| 367 | return NULL; | ||
| 368 | } | ||
| 369 | ret.p->tdata = tdata; | ||
| 370 | ret.p->thr_uid = tdata->thr_uid; | ||
| 371 | ret.p->thr_discrim = tdata->thr_discrim; | ||
| 372 | ret.p->recent_count = 0; | ||
| 373 | memset(&ret.p->cnts, 0, sizeof(prof_cnt_t)); | ||
| 374 | ret.p->gctx = gctx; | ||
| 375 | ret.p->tctx_uid = tdata->tctx_uid_next++; | ||
| 376 | ret.p->prepared = true; | ||
| 377 | ret.p->state = prof_tctx_state_initializing; | ||
| 378 | malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock); | ||
| 379 | error = ckh_insert(tsd, &tdata->bt2tctx, btkey, ret.v); | ||
| 380 | malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); | ||
| 381 | if (error) { | ||
| 382 | if (new_gctx) { | ||
| 383 | prof_gctx_try_destroy(tsd, tdata, gctx); | ||
| 384 | } | ||
| 385 | idalloctm(tsd_tsdn(tsd), ret.v, NULL, NULL, true, true); | ||
| 386 | return NULL; | ||
| 387 | } | ||
| 388 | malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); | ||
| 389 | ret.p->state = prof_tctx_state_nominal; | ||
| 390 | tctx_tree_insert(&gctx->tctxs, ret.p); | ||
| 391 | gctx->nlimbo--; | ||
| 392 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); | ||
| 393 | } | ||
| 394 | |||
| 395 | return ret.p; | ||
| 396 | } | ||
| 397 | |||
| 398 | /* Used in unit tests. */ | ||
| 399 | static prof_tdata_t * | ||
| 400 | prof_tdata_count_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, | ||
| 401 | void *arg) { | ||
| 402 | size_t *tdata_count = (size_t *)arg; | ||
| 403 | |||
| 404 | (*tdata_count)++; | ||
| 405 | |||
| 406 | return NULL; | ||
| 407 | } | ||
| 408 | |||
| 409 | /* Used in unit tests. */ | ||
| 410 | size_t | ||
| 411 | prof_tdata_count(void) { | ||
| 412 | size_t tdata_count = 0; | ||
| 413 | tsdn_t *tsdn; | ||
| 414 | |||
| 415 | tsdn = tsdn_fetch(); | ||
| 416 | malloc_mutex_lock(tsdn, &tdatas_mtx); | ||
| 417 | tdata_tree_iter(&tdatas, NULL, prof_tdata_count_iter, | ||
| 418 | (void *)&tdata_count); | ||
| 419 | malloc_mutex_unlock(tsdn, &tdatas_mtx); | ||
| 420 | |||
| 421 | return tdata_count; | ||
| 422 | } | ||
| 423 | |||
| 424 | /* Used in unit tests. */ | ||
| 425 | size_t | ||
| 426 | prof_bt_count(void) { | ||
| 427 | size_t bt_count; | ||
| 428 | tsd_t *tsd; | ||
| 429 | prof_tdata_t *tdata; | ||
| 430 | |||
| 431 | tsd = tsd_fetch(); | ||
| 432 | tdata = prof_tdata_get(tsd, false); | ||
| 433 | if (tdata == NULL) { | ||
| 434 | return 0; | ||
| 435 | } | ||
| 436 | |||
| 437 | malloc_mutex_lock(tsd_tsdn(tsd), &bt2gctx_mtx); | ||
| 438 | bt_count = ckh_count(&bt2gctx); | ||
| 439 | malloc_mutex_unlock(tsd_tsdn(tsd), &bt2gctx_mtx); | ||
| 440 | |||
| 441 | return bt_count; | ||
| 442 | } | ||
| 443 | |||
| 444 | char * | ||
| 445 | prof_thread_name_alloc(tsd_t *tsd, const char *thread_name) { | ||
| 446 | char *ret; | ||
| 447 | size_t size; | ||
| 448 | |||
| 449 | if (thread_name == NULL) { | ||
| 450 | return NULL; | ||
| 451 | } | ||
| 452 | |||
| 453 | size = strlen(thread_name) + 1; | ||
| 454 | if (size == 1) { | ||
| 455 | return ""; | ||
| 456 | } | ||
| 457 | |||
| 458 | ret = iallocztm(tsd_tsdn(tsd), size, sz_size2index(size), false, NULL, | ||
| 459 | true, arena_get(TSDN_NULL, 0, true), true); | ||
| 460 | if (ret == NULL) { | ||
| 461 | return NULL; | ||
| 462 | } | ||
| 463 | memcpy(ret, thread_name, size); | ||
| 464 | return ret; | ||
| 465 | } | ||
| 466 | |||
| 467 | int | ||
| 468 | prof_thread_name_set_impl(tsd_t *tsd, const char *thread_name) { | ||
| 469 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 470 | |||
| 471 | prof_tdata_t *tdata; | ||
| 472 | unsigned i; | ||
| 473 | char *s; | ||
| 474 | |||
| 475 | tdata = prof_tdata_get(tsd, true); | ||
| 476 | if (tdata == NULL) { | ||
| 477 | return EAGAIN; | ||
| 478 | } | ||
| 479 | |||
| 480 | /* Validate input. */ | ||
| 481 | if (thread_name == NULL) { | ||
| 482 | return EFAULT; | ||
| 483 | } | ||
| 484 | for (i = 0; thread_name[i] != '\0'; i++) { | ||
| 485 | char c = thread_name[i]; | ||
| 486 | if (!isgraph(c) && !isblank(c)) { | ||
| 487 | return EFAULT; | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 491 | s = prof_thread_name_alloc(tsd, thread_name); | ||
| 492 | if (s == NULL) { | ||
| 493 | return EAGAIN; | ||
| 494 | } | ||
| 495 | |||
| 496 | if (tdata->thread_name != NULL) { | ||
| 497 | idalloctm(tsd_tsdn(tsd), tdata->thread_name, NULL, NULL, true, | ||
| 498 | true); | ||
| 499 | tdata->thread_name = NULL; | ||
| 500 | } | ||
| 501 | if (strlen(s) > 0) { | ||
| 502 | tdata->thread_name = s; | ||
| 503 | } | ||
| 504 | return 0; | ||
| 505 | } | ||
| 506 | |||
| 507 | JEMALLOC_FORMAT_PRINTF(3, 4) | ||
| 508 | static void | ||
| 509 | prof_dump_printf(write_cb_t *prof_dump_write, void *cbopaque, | ||
| 510 | const char *format, ...) { | ||
| 511 | va_list ap; | ||
| 512 | char buf[PROF_PRINTF_BUFSIZE]; | ||
| 513 | |||
| 514 | va_start(ap, format); | ||
| 515 | malloc_vsnprintf(buf, sizeof(buf), format, ap); | ||
| 516 | va_end(ap); | ||
| 517 | prof_dump_write(cbopaque, buf); | ||
| 518 | } | ||
| 519 | |||
| 520 | /* | ||
| 521 | * Casting a double to a uint64_t may not necessarily be in range; this can be | ||
| 522 | * UB. I don't think this is practically possible with the cur counters, but | ||
| 523 | * plausibly could be with the accum counters. | ||
| 524 | */ | ||
| 525 | #ifdef JEMALLOC_PROF | ||
| 526 | static uint64_t | ||
| 527 | prof_double_uint64_cast(double d) { | ||
| 528 | /* | ||
| 529 | * Note: UINT64_MAX + 1 is exactly representable as a double on all | ||
| 530 | * reasonable platforms (certainly those we'll support). Writing this | ||
| 531 | * as !(a < b) instead of (a >= b) means that we're NaN-safe. | ||
| 532 | */ | ||
| 533 | double rounded = round(d); | ||
| 534 | if (!(rounded < (double)UINT64_MAX)) { | ||
| 535 | return UINT64_MAX; | ||
| 536 | } | ||
| 537 | return (uint64_t)rounded; | ||
| 538 | } | ||
| 539 | #endif | ||
| 540 | |||
| 541 | void prof_unbias_map_init() { | ||
| 542 | /* See the comment in prof_sample_new_event_wait */ | ||
| 543 | #ifdef JEMALLOC_PROF | ||
| 544 | for (szind_t i = 0; i < SC_NSIZES; i++) { | ||
| 545 | double sz = (double)sz_index2size(i); | ||
| 546 | double rate = (double)(ZU(1) << lg_prof_sample); | ||
| 547 | double div_val = 1.0 - exp(-sz / rate); | ||
| 548 | double unbiased_sz = sz / div_val; | ||
| 549 | /* | ||
| 550 | * The "true" right value for the unbiased count is | ||
| 551 | * 1.0/(1 - exp(-sz/rate)). The problem is, we keep the counts | ||
| 552 | * as integers (for a variety of reasons -- rounding errors | ||
| 553 | * could trigger asserts, and not all libcs can properly handle | ||
| 554 | * floating point arithmetic during malloc calls inside libc). | ||
| 555 | * Rounding to an integer, though, can lead to rounding errors | ||
| 556 | * of over 30% for sizes close to the sampling rate. So | ||
| 557 | * instead, we multiply by a constant, dividing the maximum | ||
| 558 | * possible roundoff error by that constant. To avoid overflow | ||
| 559 | * in summing up size_t values, the largest safe constant we can | ||
| 560 | * pick is the size of the smallest allocation. | ||
| 561 | */ | ||
| 562 | double cnt_shift = (double)(ZU(1) << SC_LG_TINY_MIN); | ||
| 563 | double shifted_unbiased_cnt = cnt_shift / div_val; | ||
| 564 | prof_unbiased_sz[i] = (size_t)round(unbiased_sz); | ||
| 565 | prof_shifted_unbiased_cnt[i] = (size_t)round( | ||
| 566 | shifted_unbiased_cnt); | ||
| 567 | } | ||
| 568 | #else | ||
| 569 | unreachable(); | ||
| 570 | #endif | ||
| 571 | } | ||
| 572 | |||
| 573 | /* | ||
| 574 | * The unbiasing story is long. The jeprof unbiasing logic was copied from | ||
| 575 | * pprof. Both shared an issue: they unbiased using the average size of the | ||
| 576 | * allocations at a particular stack trace. This can work out OK if allocations | ||
| 577 | * are mostly of the same size given some stack, but not otherwise. We now | ||
| 578 | * internally track what the unbiased results ought to be. We can't just report | ||
| 579 | * them as they are though; they'll still go through the jeprof unbiasing | ||
| 580 | * process. Instead, we figure out what values we can feed *into* jeprof's | ||
| 581 | * unbiasing mechanism that will lead to getting the right values out. | ||
| 582 | * | ||
| 583 | * It'll unbias count and aggregate size as: | ||
| 584 | * | ||
| 585 | * c_out = c_in * 1/(1-exp(-s_in/c_in/R) | ||
| 586 | * s_out = s_in * 1/(1-exp(-s_in/c_in/R) | ||
| 587 | * | ||
| 588 | * We want to solve for the values of c_in and s_in that will | ||
| 589 | * give the c_out and s_out that we've computed internally. | ||
| 590 | * | ||
| 591 | * Let's do a change of variables (both to make the math easier and to make it | ||
| 592 | * easier to write): | ||
| 593 | * x = s_in / c_in | ||
| 594 | * y = s_in | ||
| 595 | * k = 1/R. | ||
| 596 | * | ||
| 597 | * Then | ||
| 598 | * c_out = y/x * 1/(1-exp(-k*x)) | ||
| 599 | * s_out = y * 1/(1-exp(-k*x)) | ||
| 600 | * | ||
| 601 | * The first equation gives: | ||
| 602 | * y = x * c_out * (1-exp(-k*x)) | ||
| 603 | * The second gives: | ||
| 604 | * y = s_out * (1-exp(-k*x)) | ||
| 605 | * So we have | ||
| 606 | * x = s_out / c_out. | ||
| 607 | * And all the other values fall out from that. | ||
| 608 | * | ||
| 609 | * This is all a fair bit of work. The thing we get out of it is that we don't | ||
| 610 | * break backwards compatibility with jeprof (and the various tools that have | ||
| 611 | * copied its unbiasing logic). Eventually, we anticipate a v3 heap profile | ||
| 612 | * dump format based on JSON, at which point I think much of this logic can get | ||
| 613 | * cleaned up (since we'll be taking a compatibility break there anyways). | ||
| 614 | */ | ||
| 615 | static void | ||
| 616 | prof_do_unbias(uint64_t c_out_shifted_i, uint64_t s_out_i, uint64_t *r_c_in, | ||
| 617 | uint64_t *r_s_in) { | ||
| 618 | #ifdef JEMALLOC_PROF | ||
| 619 | if (c_out_shifted_i == 0 || s_out_i == 0) { | ||
| 620 | *r_c_in = 0; | ||
| 621 | *r_s_in = 0; | ||
| 622 | return; | ||
| 623 | } | ||
| 624 | /* | ||
| 625 | * See the note in prof_unbias_map_init() to see why we take c_out in a | ||
| 626 | * shifted form. | ||
| 627 | */ | ||
| 628 | double c_out = (double)c_out_shifted_i | ||
| 629 | / (double)(ZU(1) << SC_LG_TINY_MIN); | ||
| 630 | double s_out = (double)s_out_i; | ||
| 631 | double R = (double)(ZU(1) << lg_prof_sample); | ||
| 632 | |||
| 633 | double x = s_out / c_out; | ||
| 634 | double y = s_out * (1.0 - exp(-x / R)); | ||
| 635 | |||
| 636 | double c_in = y / x; | ||
| 637 | double s_in = y; | ||
| 638 | |||
| 639 | *r_c_in = prof_double_uint64_cast(c_in); | ||
| 640 | *r_s_in = prof_double_uint64_cast(s_in); | ||
| 641 | #else | ||
| 642 | unreachable(); | ||
| 643 | #endif | ||
| 644 | } | ||
| 645 | |||
| 646 | static void | ||
| 647 | prof_dump_print_cnts(write_cb_t *prof_dump_write, void *cbopaque, | ||
| 648 | const prof_cnt_t *cnts) { | ||
| 649 | uint64_t curobjs; | ||
| 650 | uint64_t curbytes; | ||
| 651 | uint64_t accumobjs; | ||
| 652 | uint64_t accumbytes; | ||
| 653 | if (opt_prof_unbias) { | ||
| 654 | prof_do_unbias(cnts->curobjs_shifted_unbiased, | ||
| 655 | cnts->curbytes_unbiased, &curobjs, &curbytes); | ||
| 656 | prof_do_unbias(cnts->accumobjs_shifted_unbiased, | ||
| 657 | cnts->accumbytes_unbiased, &accumobjs, &accumbytes); | ||
| 658 | } else { | ||
| 659 | curobjs = cnts->curobjs; | ||
| 660 | curbytes = cnts->curbytes; | ||
| 661 | accumobjs = cnts->accumobjs; | ||
| 662 | accumbytes = cnts->accumbytes; | ||
| 663 | } | ||
| 664 | prof_dump_printf(prof_dump_write, cbopaque, | ||
| 665 | "%"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]", | ||
| 666 | curobjs, curbytes, accumobjs, accumbytes); | ||
| 667 | } | ||
| 668 | |||
| 669 | static void | ||
| 670 | prof_tctx_merge_tdata(tsdn_t *tsdn, prof_tctx_t *tctx, prof_tdata_t *tdata) { | ||
| 671 | malloc_mutex_assert_owner(tsdn, tctx->tdata->lock); | ||
| 672 | |||
| 673 | malloc_mutex_lock(tsdn, tctx->gctx->lock); | ||
| 674 | |||
| 675 | switch (tctx->state) { | ||
| 676 | case prof_tctx_state_initializing: | ||
| 677 | malloc_mutex_unlock(tsdn, tctx->gctx->lock); | ||
| 678 | return; | ||
| 679 | case prof_tctx_state_nominal: | ||
| 680 | tctx->state = prof_tctx_state_dumping; | ||
| 681 | malloc_mutex_unlock(tsdn, tctx->gctx->lock); | ||
| 682 | |||
| 683 | memcpy(&tctx->dump_cnts, &tctx->cnts, sizeof(prof_cnt_t)); | ||
| 684 | |||
| 685 | tdata->cnt_summed.curobjs += tctx->dump_cnts.curobjs; | ||
| 686 | tdata->cnt_summed.curobjs_shifted_unbiased | ||
| 687 | += tctx->dump_cnts.curobjs_shifted_unbiased; | ||
| 688 | tdata->cnt_summed.curbytes += tctx->dump_cnts.curbytes; | ||
| 689 | tdata->cnt_summed.curbytes_unbiased | ||
| 690 | += tctx->dump_cnts.curbytes_unbiased; | ||
| 691 | if (opt_prof_accum) { | ||
| 692 | tdata->cnt_summed.accumobjs += | ||
| 693 | tctx->dump_cnts.accumobjs; | ||
| 694 | tdata->cnt_summed.accumobjs_shifted_unbiased += | ||
| 695 | tctx->dump_cnts.accumobjs_shifted_unbiased; | ||
| 696 | tdata->cnt_summed.accumbytes += | ||
| 697 | tctx->dump_cnts.accumbytes; | ||
| 698 | tdata->cnt_summed.accumbytes_unbiased += | ||
| 699 | tctx->dump_cnts.accumbytes_unbiased; | ||
| 700 | } | ||
| 701 | break; | ||
| 702 | case prof_tctx_state_dumping: | ||
| 703 | case prof_tctx_state_purgatory: | ||
| 704 | not_reached(); | ||
| 705 | } | ||
| 706 | } | ||
| 707 | |||
| 708 | static void | ||
| 709 | prof_tctx_merge_gctx(tsdn_t *tsdn, prof_tctx_t *tctx, prof_gctx_t *gctx) { | ||
| 710 | malloc_mutex_assert_owner(tsdn, gctx->lock); | ||
| 711 | |||
| 712 | gctx->cnt_summed.curobjs += tctx->dump_cnts.curobjs; | ||
| 713 | gctx->cnt_summed.curobjs_shifted_unbiased | ||
| 714 | += tctx->dump_cnts.curobjs_shifted_unbiased; | ||
| 715 | gctx->cnt_summed.curbytes += tctx->dump_cnts.curbytes; | ||
| 716 | gctx->cnt_summed.curbytes_unbiased += tctx->dump_cnts.curbytes_unbiased; | ||
| 717 | if (opt_prof_accum) { | ||
| 718 | gctx->cnt_summed.accumobjs += tctx->dump_cnts.accumobjs; | ||
| 719 | gctx->cnt_summed.accumobjs_shifted_unbiased | ||
| 720 | += tctx->dump_cnts.accumobjs_shifted_unbiased; | ||
| 721 | gctx->cnt_summed.accumbytes += tctx->dump_cnts.accumbytes; | ||
| 722 | gctx->cnt_summed.accumbytes_unbiased | ||
| 723 | += tctx->dump_cnts.accumbytes_unbiased; | ||
| 724 | } | ||
| 725 | } | ||
| 726 | |||
| 727 | static prof_tctx_t * | ||
| 728 | prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) { | ||
| 729 | tsdn_t *tsdn = (tsdn_t *)arg; | ||
| 730 | |||
| 731 | malloc_mutex_assert_owner(tsdn, tctx->gctx->lock); | ||
| 732 | |||
| 733 | switch (tctx->state) { | ||
| 734 | case prof_tctx_state_nominal: | ||
| 735 | /* New since dumping started; ignore. */ | ||
| 736 | break; | ||
| 737 | case prof_tctx_state_dumping: | ||
| 738 | case prof_tctx_state_purgatory: | ||
| 739 | prof_tctx_merge_gctx(tsdn, tctx, tctx->gctx); | ||
| 740 | break; | ||
| 741 | default: | ||
| 742 | not_reached(); | ||
| 743 | } | ||
| 744 | |||
| 745 | return NULL; | ||
| 746 | } | ||
| 747 | |||
| 748 | typedef struct prof_dump_iter_arg_s prof_dump_iter_arg_t; | ||
| 749 | struct prof_dump_iter_arg_s { | ||
| 750 | tsdn_t *tsdn; | ||
| 751 | write_cb_t *prof_dump_write; | ||
| 752 | void *cbopaque; | ||
| 753 | }; | ||
| 754 | |||
| 755 | static prof_tctx_t * | ||
| 756 | prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *opaque) { | ||
| 757 | prof_dump_iter_arg_t *arg = (prof_dump_iter_arg_t *)opaque; | ||
| 758 | malloc_mutex_assert_owner(arg->tsdn, tctx->gctx->lock); | ||
| 759 | |||
| 760 | switch (tctx->state) { | ||
| 761 | case prof_tctx_state_initializing: | ||
| 762 | case prof_tctx_state_nominal: | ||
| 763 | /* Not captured by this dump. */ | ||
| 764 | break; | ||
| 765 | case prof_tctx_state_dumping: | ||
| 766 | case prof_tctx_state_purgatory: | ||
| 767 | prof_dump_printf(arg->prof_dump_write, arg->cbopaque, | ||
| 768 | " t%"FMTu64": ", tctx->thr_uid); | ||
| 769 | prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, | ||
| 770 | &tctx->dump_cnts); | ||
| 771 | arg->prof_dump_write(arg->cbopaque, "\n"); | ||
| 772 | break; | ||
| 773 | default: | ||
| 774 | not_reached(); | ||
| 775 | } | ||
| 776 | return NULL; | ||
| 777 | } | ||
| 778 | |||
| 779 | static prof_tctx_t * | ||
| 780 | prof_tctx_finish_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) { | ||
| 781 | tsdn_t *tsdn = (tsdn_t *)arg; | ||
| 782 | prof_tctx_t *ret; | ||
| 783 | |||
| 784 | malloc_mutex_assert_owner(tsdn, tctx->gctx->lock); | ||
| 785 | |||
| 786 | switch (tctx->state) { | ||
| 787 | case prof_tctx_state_nominal: | ||
| 788 | /* New since dumping started; ignore. */ | ||
| 789 | break; | ||
| 790 | case prof_tctx_state_dumping: | ||
| 791 | tctx->state = prof_tctx_state_nominal; | ||
| 792 | break; | ||
| 793 | case prof_tctx_state_purgatory: | ||
| 794 | ret = tctx; | ||
| 795 | goto label_return; | ||
| 796 | default: | ||
| 797 | not_reached(); | ||
| 798 | } | ||
| 799 | |||
| 800 | ret = NULL; | ||
| 801 | label_return: | ||
| 802 | return ret; | ||
| 803 | } | ||
| 804 | |||
| 805 | static void | ||
| 806 | prof_dump_gctx_prep(tsdn_t *tsdn, prof_gctx_t *gctx, prof_gctx_tree_t *gctxs) { | ||
| 807 | cassert(config_prof); | ||
| 808 | |||
| 809 | malloc_mutex_lock(tsdn, gctx->lock); | ||
| 810 | |||
| 811 | /* | ||
| 812 | * Increment nlimbo so that gctx won't go away before dump. | ||
| 813 | * Additionally, link gctx into the dump list so that it is included in | ||
| 814 | * prof_dump()'s second pass. | ||
| 815 | */ | ||
| 816 | gctx->nlimbo++; | ||
| 817 | gctx_tree_insert(gctxs, gctx); | ||
| 818 | |||
| 819 | memset(&gctx->cnt_summed, 0, sizeof(prof_cnt_t)); | ||
| 820 | |||
| 821 | malloc_mutex_unlock(tsdn, gctx->lock); | ||
| 822 | } | ||
| 823 | |||
| 824 | typedef struct prof_gctx_merge_iter_arg_s prof_gctx_merge_iter_arg_t; | ||
| 825 | struct prof_gctx_merge_iter_arg_s { | ||
| 826 | tsdn_t *tsdn; | ||
| 827 | size_t *leak_ngctx; | ||
| 828 | }; | ||
| 829 | |||
| 830 | static prof_gctx_t * | ||
| 831 | prof_gctx_merge_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) { | ||
| 832 | prof_gctx_merge_iter_arg_t *arg = (prof_gctx_merge_iter_arg_t *)opaque; | ||
| 833 | |||
| 834 | malloc_mutex_lock(arg->tsdn, gctx->lock); | ||
| 835 | tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_merge_iter, | ||
| 836 | (void *)arg->tsdn); | ||
| 837 | if (gctx->cnt_summed.curobjs != 0) { | ||
| 838 | (*arg->leak_ngctx)++; | ||
| 839 | } | ||
| 840 | malloc_mutex_unlock(arg->tsdn, gctx->lock); | ||
| 841 | |||
| 842 | return NULL; | ||
| 843 | } | ||
| 844 | |||
| 845 | static void | ||
| 846 | prof_gctx_finish(tsd_t *tsd, prof_gctx_tree_t *gctxs) { | ||
| 847 | prof_tdata_t *tdata = prof_tdata_get(tsd, false); | ||
| 848 | prof_gctx_t *gctx; | ||
| 849 | |||
| 850 | /* | ||
| 851 | * Standard tree iteration won't work here, because as soon as we | ||
| 852 | * decrement gctx->nlimbo and unlock gctx, another thread can | ||
| 853 | * concurrently destroy it, which will corrupt the tree. Therefore, | ||
| 854 | * tear down the tree one node at a time during iteration. | ||
| 855 | */ | ||
| 856 | while ((gctx = gctx_tree_first(gctxs)) != NULL) { | ||
| 857 | gctx_tree_remove(gctxs, gctx); | ||
| 858 | malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); | ||
| 859 | { | ||
| 860 | prof_tctx_t *next; | ||
| 861 | |||
| 862 | next = NULL; | ||
| 863 | do { | ||
| 864 | prof_tctx_t *to_destroy = | ||
| 865 | tctx_tree_iter(&gctx->tctxs, next, | ||
| 866 | prof_tctx_finish_iter, | ||
| 867 | (void *)tsd_tsdn(tsd)); | ||
| 868 | if (to_destroy != NULL) { | ||
| 869 | next = tctx_tree_next(&gctx->tctxs, | ||
| 870 | to_destroy); | ||
| 871 | tctx_tree_remove(&gctx->tctxs, | ||
| 872 | to_destroy); | ||
| 873 | idalloctm(tsd_tsdn(tsd), to_destroy, | ||
| 874 | NULL, NULL, true, true); | ||
| 875 | } else { | ||
| 876 | next = NULL; | ||
| 877 | } | ||
| 878 | } while (next != NULL); | ||
| 879 | } | ||
| 880 | gctx->nlimbo--; | ||
| 881 | if (prof_gctx_should_destroy(gctx)) { | ||
| 882 | gctx->nlimbo++; | ||
| 883 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); | ||
| 884 | prof_gctx_try_destroy(tsd, tdata, gctx); | ||
| 885 | } else { | ||
| 886 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); | ||
| 887 | } | ||
| 888 | } | ||
| 889 | } | ||
| 890 | |||
| 891 | typedef struct prof_tdata_merge_iter_arg_s prof_tdata_merge_iter_arg_t; | ||
| 892 | struct prof_tdata_merge_iter_arg_s { | ||
| 893 | tsdn_t *tsdn; | ||
| 894 | prof_cnt_t *cnt_all; | ||
| 895 | }; | ||
| 896 | |||
| 897 | static prof_tdata_t * | ||
| 898 | prof_tdata_merge_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, | ||
| 899 | void *opaque) { | ||
| 900 | prof_tdata_merge_iter_arg_t *arg = | ||
| 901 | (prof_tdata_merge_iter_arg_t *)opaque; | ||
| 902 | |||
| 903 | malloc_mutex_lock(arg->tsdn, tdata->lock); | ||
| 904 | if (!tdata->expired) { | ||
| 905 | size_t tabind; | ||
| 906 | union { | ||
| 907 | prof_tctx_t *p; | ||
| 908 | void *v; | ||
| 909 | } tctx; | ||
| 910 | |||
| 911 | tdata->dumping = true; | ||
| 912 | memset(&tdata->cnt_summed, 0, sizeof(prof_cnt_t)); | ||
| 913 | for (tabind = 0; !ckh_iter(&tdata->bt2tctx, &tabind, NULL, | ||
| 914 | &tctx.v);) { | ||
| 915 | prof_tctx_merge_tdata(arg->tsdn, tctx.p, tdata); | ||
| 916 | } | ||
| 917 | |||
| 918 | arg->cnt_all->curobjs += tdata->cnt_summed.curobjs; | ||
| 919 | arg->cnt_all->curobjs_shifted_unbiased | ||
| 920 | += tdata->cnt_summed.curobjs_shifted_unbiased; | ||
| 921 | arg->cnt_all->curbytes += tdata->cnt_summed.curbytes; | ||
| 922 | arg->cnt_all->curbytes_unbiased | ||
| 923 | += tdata->cnt_summed.curbytes_unbiased; | ||
| 924 | if (opt_prof_accum) { | ||
| 925 | arg->cnt_all->accumobjs += tdata->cnt_summed.accumobjs; | ||
| 926 | arg->cnt_all->accumobjs_shifted_unbiased | ||
| 927 | += tdata->cnt_summed.accumobjs_shifted_unbiased; | ||
| 928 | arg->cnt_all->accumbytes += | ||
| 929 | tdata->cnt_summed.accumbytes; | ||
| 930 | arg->cnt_all->accumbytes_unbiased += | ||
| 931 | tdata->cnt_summed.accumbytes_unbiased; | ||
| 932 | } | ||
| 933 | } else { | ||
| 934 | tdata->dumping = false; | ||
| 935 | } | ||
| 936 | malloc_mutex_unlock(arg->tsdn, tdata->lock); | ||
| 937 | |||
| 938 | return NULL; | ||
| 939 | } | ||
| 940 | |||
| 941 | static prof_tdata_t * | ||
| 942 | prof_tdata_dump_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, | ||
| 943 | void *opaque) { | ||
| 944 | if (!tdata->dumping) { | ||
| 945 | return NULL; | ||
| 946 | } | ||
| 947 | |||
| 948 | prof_dump_iter_arg_t *arg = (prof_dump_iter_arg_t *)opaque; | ||
| 949 | prof_dump_printf(arg->prof_dump_write, arg->cbopaque, " t%"FMTu64": ", | ||
| 950 | tdata->thr_uid); | ||
| 951 | prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, | ||
| 952 | &tdata->cnt_summed); | ||
| 953 | if (tdata->thread_name != NULL) { | ||
| 954 | arg->prof_dump_write(arg->cbopaque, " "); | ||
| 955 | arg->prof_dump_write(arg->cbopaque, tdata->thread_name); | ||
| 956 | } | ||
| 957 | arg->prof_dump_write(arg->cbopaque, "\n"); | ||
| 958 | return NULL; | ||
| 959 | } | ||
| 960 | |||
| 961 | static void | ||
| 962 | prof_dump_header(prof_dump_iter_arg_t *arg, const prof_cnt_t *cnt_all) { | ||
| 963 | prof_dump_printf(arg->prof_dump_write, arg->cbopaque, | ||
| 964 | "heap_v2/%"FMTu64"\n t*: ", ((uint64_t)1U << lg_prof_sample)); | ||
| 965 | prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, cnt_all); | ||
| 966 | arg->prof_dump_write(arg->cbopaque, "\n"); | ||
| 967 | |||
| 968 | malloc_mutex_lock(arg->tsdn, &tdatas_mtx); | ||
| 969 | tdata_tree_iter(&tdatas, NULL, prof_tdata_dump_iter, arg); | ||
| 970 | malloc_mutex_unlock(arg->tsdn, &tdatas_mtx); | ||
| 971 | } | ||
| 972 | |||
| 973 | static void | ||
| 974 | prof_dump_gctx(prof_dump_iter_arg_t *arg, prof_gctx_t *gctx, | ||
| 975 | const prof_bt_t *bt, prof_gctx_tree_t *gctxs) { | ||
| 976 | cassert(config_prof); | ||
| 977 | malloc_mutex_assert_owner(arg->tsdn, gctx->lock); | ||
| 978 | |||
| 979 | /* Avoid dumping such gctx's that have no useful data. */ | ||
| 980 | if ((!opt_prof_accum && gctx->cnt_summed.curobjs == 0) || | ||
| 981 | (opt_prof_accum && gctx->cnt_summed.accumobjs == 0)) { | ||
| 982 | assert(gctx->cnt_summed.curobjs == 0); | ||
| 983 | assert(gctx->cnt_summed.curbytes == 0); | ||
| 984 | /* | ||
| 985 | * These asserts would not be correct -- see the comment on races | ||
| 986 | * in prof.c | ||
| 987 | * assert(gctx->cnt_summed.curobjs_unbiased == 0); | ||
| 988 | * assert(gctx->cnt_summed.curbytes_unbiased == 0); | ||
| 989 | */ | ||
| 990 | assert(gctx->cnt_summed.accumobjs == 0); | ||
| 991 | assert(gctx->cnt_summed.accumobjs_shifted_unbiased == 0); | ||
| 992 | assert(gctx->cnt_summed.accumbytes == 0); | ||
| 993 | assert(gctx->cnt_summed.accumbytes_unbiased == 0); | ||
| 994 | return; | ||
| 995 | } | ||
| 996 | |||
| 997 | arg->prof_dump_write(arg->cbopaque, "@"); | ||
| 998 | for (unsigned i = 0; i < bt->len; i++) { | ||
| 999 | prof_dump_printf(arg->prof_dump_write, arg->cbopaque, | ||
| 1000 | " %#"FMTxPTR, (uintptr_t)bt->vec[i]); | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | arg->prof_dump_write(arg->cbopaque, "\n t*: "); | ||
| 1004 | prof_dump_print_cnts(arg->prof_dump_write, arg->cbopaque, | ||
| 1005 | &gctx->cnt_summed); | ||
| 1006 | arg->prof_dump_write(arg->cbopaque, "\n"); | ||
| 1007 | |||
| 1008 | tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_dump_iter, arg); | ||
| 1009 | } | ||
| 1010 | |||
| 1011 | /* | ||
| 1012 | * See prof_sample_new_event_wait() comment for why the body of this function | ||
| 1013 | * is conditionally compiled. | ||
| 1014 | */ | ||
| 1015 | static void | ||
| 1016 | prof_leakcheck(const prof_cnt_t *cnt_all, size_t leak_ngctx) { | ||
| 1017 | #ifdef JEMALLOC_PROF | ||
| 1018 | /* | ||
| 1019 | * Scaling is equivalent AdjustSamples() in jeprof, but the result may | ||
| 1020 | * differ slightly from what jeprof reports, because here we scale the | ||
| 1021 | * summary values, whereas jeprof scales each context individually and | ||
| 1022 | * reports the sums of the scaled values. | ||
| 1023 | */ | ||
| 1024 | if (cnt_all->curbytes != 0) { | ||
| 1025 | double sample_period = (double)((uint64_t)1 << lg_prof_sample); | ||
| 1026 | double ratio = (((double)cnt_all->curbytes) / | ||
| 1027 | (double)cnt_all->curobjs) / sample_period; | ||
| 1028 | double scale_factor = 1.0 / (1.0 - exp(-ratio)); | ||
| 1029 | uint64_t curbytes = (uint64_t)round(((double)cnt_all->curbytes) | ||
| 1030 | * scale_factor); | ||
| 1031 | uint64_t curobjs = (uint64_t)round(((double)cnt_all->curobjs) * | ||
| 1032 | scale_factor); | ||
| 1033 | |||
| 1034 | malloc_printf("<jemalloc>: Leak approximation summary: ~%"FMTu64 | ||
| 1035 | " byte%s, ~%"FMTu64" object%s, >= %zu context%s\n", | ||
| 1036 | curbytes, (curbytes != 1) ? "s" : "", curobjs, (curobjs != | ||
| 1037 | 1) ? "s" : "", leak_ngctx, (leak_ngctx != 1) ? "s" : ""); | ||
| 1038 | malloc_printf( | ||
| 1039 | "<jemalloc>: Run jeprof on dump output for leak detail\n"); | ||
| 1040 | if (opt_prof_leak_error) { | ||
| 1041 | malloc_printf( | ||
| 1042 | "<jemalloc>: Exiting with error code because memory" | ||
| 1043 | " leaks were detected\n"); | ||
| 1044 | /* | ||
| 1045 | * Use _exit() with underscore to avoid calling atexit() | ||
| 1046 | * and entering endless cycle. | ||
| 1047 | */ | ||
| 1048 | _exit(1); | ||
| 1049 | } | ||
| 1050 | } | ||
| 1051 | #endif | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | static prof_gctx_t * | ||
| 1055 | prof_gctx_dump_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) { | ||
| 1056 | prof_dump_iter_arg_t *arg = (prof_dump_iter_arg_t *)opaque; | ||
| 1057 | malloc_mutex_lock(arg->tsdn, gctx->lock); | ||
| 1058 | prof_dump_gctx(arg, gctx, &gctx->bt, gctxs); | ||
| 1059 | malloc_mutex_unlock(arg->tsdn, gctx->lock); | ||
| 1060 | return NULL; | ||
| 1061 | } | ||
| 1062 | |||
| 1063 | static void | ||
| 1064 | prof_dump_prep(tsd_t *tsd, prof_tdata_t *tdata, prof_cnt_t *cnt_all, | ||
| 1065 | size_t *leak_ngctx, prof_gctx_tree_t *gctxs) { | ||
| 1066 | size_t tabind; | ||
| 1067 | union { | ||
| 1068 | prof_gctx_t *p; | ||
| 1069 | void *v; | ||
| 1070 | } gctx; | ||
| 1071 | |||
| 1072 | prof_enter(tsd, tdata); | ||
| 1073 | |||
| 1074 | /* | ||
| 1075 | * Put gctx's in limbo and clear their counters in preparation for | ||
| 1076 | * summing. | ||
| 1077 | */ | ||
| 1078 | gctx_tree_new(gctxs); | ||
| 1079 | for (tabind = 0; !ckh_iter(&bt2gctx, &tabind, NULL, &gctx.v);) { | ||
| 1080 | prof_dump_gctx_prep(tsd_tsdn(tsd), gctx.p, gctxs); | ||
| 1081 | } | ||
| 1082 | |||
| 1083 | /* | ||
| 1084 | * Iterate over tdatas, and for the non-expired ones snapshot their tctx | ||
| 1085 | * stats and merge them into the associated gctx's. | ||
| 1086 | */ | ||
| 1087 | memset(cnt_all, 0, sizeof(prof_cnt_t)); | ||
| 1088 | prof_tdata_merge_iter_arg_t prof_tdata_merge_iter_arg = {tsd_tsdn(tsd), | ||
| 1089 | cnt_all}; | ||
| 1090 | malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1091 | tdata_tree_iter(&tdatas, NULL, prof_tdata_merge_iter, | ||
| 1092 | &prof_tdata_merge_iter_arg); | ||
| 1093 | malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1094 | |||
| 1095 | /* Merge tctx stats into gctx's. */ | ||
| 1096 | *leak_ngctx = 0; | ||
| 1097 | prof_gctx_merge_iter_arg_t prof_gctx_merge_iter_arg = {tsd_tsdn(tsd), | ||
| 1098 | leak_ngctx}; | ||
| 1099 | gctx_tree_iter(gctxs, NULL, prof_gctx_merge_iter, | ||
| 1100 | &prof_gctx_merge_iter_arg); | ||
| 1101 | |||
| 1102 | prof_leave(tsd, tdata); | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | void | ||
| 1106 | prof_dump_impl(tsd_t *tsd, write_cb_t *prof_dump_write, void *cbopaque, | ||
| 1107 | prof_tdata_t *tdata, bool leakcheck) { | ||
| 1108 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_dump_mtx); | ||
| 1109 | prof_cnt_t cnt_all; | ||
| 1110 | size_t leak_ngctx; | ||
| 1111 | prof_gctx_tree_t gctxs; | ||
| 1112 | prof_dump_prep(tsd, tdata, &cnt_all, &leak_ngctx, &gctxs); | ||
| 1113 | prof_dump_iter_arg_t prof_dump_iter_arg = {tsd_tsdn(tsd), | ||
| 1114 | prof_dump_write, cbopaque}; | ||
| 1115 | prof_dump_header(&prof_dump_iter_arg, &cnt_all); | ||
| 1116 | gctx_tree_iter(&gctxs, NULL, prof_gctx_dump_iter, &prof_dump_iter_arg); | ||
| 1117 | prof_gctx_finish(tsd, &gctxs); | ||
| 1118 | if (leakcheck) { | ||
| 1119 | prof_leakcheck(&cnt_all, leak_ngctx); | ||
| 1120 | } | ||
| 1121 | } | ||
| 1122 | |||
| 1123 | /* Used in unit tests. */ | ||
| 1124 | void | ||
| 1125 | prof_cnt_all(prof_cnt_t *cnt_all) { | ||
| 1126 | tsd_t *tsd = tsd_fetch(); | ||
| 1127 | prof_tdata_t *tdata = prof_tdata_get(tsd, false); | ||
| 1128 | if (tdata == NULL) { | ||
| 1129 | memset(cnt_all, 0, sizeof(prof_cnt_t)); | ||
| 1130 | } else { | ||
| 1131 | size_t leak_ngctx; | ||
| 1132 | prof_gctx_tree_t gctxs; | ||
| 1133 | prof_dump_prep(tsd, tdata, cnt_all, &leak_ngctx, &gctxs); | ||
| 1134 | prof_gctx_finish(tsd, &gctxs); | ||
| 1135 | } | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | void | ||
| 1139 | prof_bt_hash(const void *key, size_t r_hash[2]) { | ||
| 1140 | prof_bt_t *bt = (prof_bt_t *)key; | ||
| 1141 | |||
| 1142 | cassert(config_prof); | ||
| 1143 | |||
| 1144 | hash(bt->vec, bt->len * sizeof(void *), 0x94122f33U, r_hash); | ||
| 1145 | } | ||
| 1146 | |||
| 1147 | bool | ||
| 1148 | prof_bt_keycomp(const void *k1, const void *k2) { | ||
| 1149 | const prof_bt_t *bt1 = (prof_bt_t *)k1; | ||
| 1150 | const prof_bt_t *bt2 = (prof_bt_t *)k2; | ||
| 1151 | |||
| 1152 | cassert(config_prof); | ||
| 1153 | |||
| 1154 | if (bt1->len != bt2->len) { | ||
| 1155 | return false; | ||
| 1156 | } | ||
| 1157 | return (memcmp(bt1->vec, bt2->vec, bt1->len * sizeof(void *)) == 0); | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | prof_tdata_t * | ||
| 1161 | prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim, | ||
| 1162 | char *thread_name, bool active) { | ||
| 1163 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 1164 | |||
| 1165 | prof_tdata_t *tdata; | ||
| 1166 | |||
| 1167 | cassert(config_prof); | ||
| 1168 | |||
| 1169 | /* Initialize an empty cache for this thread. */ | ||
| 1170 | tdata = (prof_tdata_t *)iallocztm(tsd_tsdn(tsd), sizeof(prof_tdata_t), | ||
| 1171 | sz_size2index(sizeof(prof_tdata_t)), false, NULL, true, | ||
| 1172 | arena_get(TSDN_NULL, 0, true), true); | ||
| 1173 | if (tdata == NULL) { | ||
| 1174 | return NULL; | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | tdata->lock = prof_tdata_mutex_choose(thr_uid); | ||
| 1178 | tdata->thr_uid = thr_uid; | ||
| 1179 | tdata->thr_discrim = thr_discrim; | ||
| 1180 | tdata->thread_name = thread_name; | ||
| 1181 | tdata->attached = true; | ||
| 1182 | tdata->expired = false; | ||
| 1183 | tdata->tctx_uid_next = 0; | ||
| 1184 | |||
| 1185 | if (ckh_new(tsd, &tdata->bt2tctx, PROF_CKH_MINITEMS, prof_bt_hash, | ||
| 1186 | prof_bt_keycomp)) { | ||
| 1187 | idalloctm(tsd_tsdn(tsd), tdata, NULL, NULL, true, true); | ||
| 1188 | return NULL; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | tdata->enq = false; | ||
| 1192 | tdata->enq_idump = false; | ||
| 1193 | tdata->enq_gdump = false; | ||
| 1194 | |||
| 1195 | tdata->dumping = false; | ||
| 1196 | tdata->active = active; | ||
| 1197 | |||
| 1198 | malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1199 | tdata_tree_insert(&tdatas, tdata); | ||
| 1200 | malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1201 | |||
| 1202 | return tdata; | ||
| 1203 | } | ||
| 1204 | |||
| 1205 | static bool | ||
| 1206 | prof_tdata_should_destroy_unlocked(prof_tdata_t *tdata, bool even_if_attached) { | ||
| 1207 | if (tdata->attached && !even_if_attached) { | ||
| 1208 | return false; | ||
| 1209 | } | ||
| 1210 | if (ckh_count(&tdata->bt2tctx) != 0) { | ||
| 1211 | return false; | ||
| 1212 | } | ||
| 1213 | return true; | ||
| 1214 | } | ||
| 1215 | |||
| 1216 | static bool | ||
| 1217 | prof_tdata_should_destroy(tsdn_t *tsdn, prof_tdata_t *tdata, | ||
| 1218 | bool even_if_attached) { | ||
| 1219 | malloc_mutex_assert_owner(tsdn, tdata->lock); | ||
| 1220 | |||
| 1221 | return prof_tdata_should_destroy_unlocked(tdata, even_if_attached); | ||
| 1222 | } | ||
| 1223 | |||
| 1224 | static void | ||
| 1225 | prof_tdata_destroy_locked(tsd_t *tsd, prof_tdata_t *tdata, | ||
| 1226 | bool even_if_attached) { | ||
| 1227 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1228 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), tdata->lock); | ||
| 1229 | |||
| 1230 | tdata_tree_remove(&tdatas, tdata); | ||
| 1231 | |||
| 1232 | assert(prof_tdata_should_destroy_unlocked(tdata, even_if_attached)); | ||
| 1233 | |||
| 1234 | if (tdata->thread_name != NULL) { | ||
| 1235 | idalloctm(tsd_tsdn(tsd), tdata->thread_name, NULL, NULL, true, | ||
| 1236 | true); | ||
| 1237 | } | ||
| 1238 | ckh_delete(tsd, &tdata->bt2tctx); | ||
| 1239 | idalloctm(tsd_tsdn(tsd), tdata, NULL, NULL, true, true); | ||
| 1240 | } | ||
| 1241 | |||
| 1242 | static void | ||
| 1243 | prof_tdata_destroy(tsd_t *tsd, prof_tdata_t *tdata, bool even_if_attached) { | ||
| 1244 | malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1245 | prof_tdata_destroy_locked(tsd, tdata, even_if_attached); | ||
| 1246 | malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1247 | } | ||
| 1248 | |||
| 1249 | void | ||
| 1250 | prof_tdata_detach(tsd_t *tsd, prof_tdata_t *tdata) { | ||
| 1251 | bool destroy_tdata; | ||
| 1252 | |||
| 1253 | malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock); | ||
| 1254 | if (tdata->attached) { | ||
| 1255 | destroy_tdata = prof_tdata_should_destroy(tsd_tsdn(tsd), tdata, | ||
| 1256 | true); | ||
| 1257 | /* | ||
| 1258 | * Only detach if !destroy_tdata, because detaching would allow | ||
| 1259 | * another thread to win the race to destroy tdata. | ||
| 1260 | */ | ||
| 1261 | if (!destroy_tdata) { | ||
| 1262 | tdata->attached = false; | ||
| 1263 | } | ||
| 1264 | tsd_prof_tdata_set(tsd, NULL); | ||
| 1265 | } else { | ||
| 1266 | destroy_tdata = false; | ||
| 1267 | } | ||
| 1268 | malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); | ||
| 1269 | if (destroy_tdata) { | ||
| 1270 | prof_tdata_destroy(tsd, tdata, true); | ||
| 1271 | } | ||
| 1272 | } | ||
| 1273 | |||
| 1274 | static bool | ||
| 1275 | prof_tdata_expire(tsdn_t *tsdn, prof_tdata_t *tdata) { | ||
| 1276 | bool destroy_tdata; | ||
| 1277 | |||
| 1278 | malloc_mutex_lock(tsdn, tdata->lock); | ||
| 1279 | if (!tdata->expired) { | ||
| 1280 | tdata->expired = true; | ||
| 1281 | destroy_tdata = prof_tdata_should_destroy(tsdn, tdata, false); | ||
| 1282 | } else { | ||
| 1283 | destroy_tdata = false; | ||
| 1284 | } | ||
| 1285 | malloc_mutex_unlock(tsdn, tdata->lock); | ||
| 1286 | |||
| 1287 | return destroy_tdata; | ||
| 1288 | } | ||
| 1289 | |||
| 1290 | static prof_tdata_t * | ||
| 1291 | prof_tdata_reset_iter(prof_tdata_tree_t *tdatas_ptr, prof_tdata_t *tdata, | ||
| 1292 | void *arg) { | ||
| 1293 | tsdn_t *tsdn = (tsdn_t *)arg; | ||
| 1294 | |||
| 1295 | return (prof_tdata_expire(tsdn, tdata) ? tdata : NULL); | ||
| 1296 | } | ||
| 1297 | |||
| 1298 | void | ||
| 1299 | prof_reset(tsd_t *tsd, size_t lg_sample) { | ||
| 1300 | prof_tdata_t *next; | ||
| 1301 | |||
| 1302 | assert(lg_sample < (sizeof(uint64_t) << 3)); | ||
| 1303 | |||
| 1304 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx); | ||
| 1305 | malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1306 | |||
| 1307 | lg_prof_sample = lg_sample; | ||
| 1308 | prof_unbias_map_init(); | ||
| 1309 | |||
| 1310 | next = NULL; | ||
| 1311 | do { | ||
| 1312 | prof_tdata_t *to_destroy = tdata_tree_iter(&tdatas, next, | ||
| 1313 | prof_tdata_reset_iter, (void *)tsd); | ||
| 1314 | if (to_destroy != NULL) { | ||
| 1315 | next = tdata_tree_next(&tdatas, to_destroy); | ||
| 1316 | prof_tdata_destroy_locked(tsd, to_destroy, false); | ||
| 1317 | } else { | ||
| 1318 | next = NULL; | ||
| 1319 | } | ||
| 1320 | } while (next != NULL); | ||
| 1321 | |||
| 1322 | malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx); | ||
| 1323 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx); | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | static bool | ||
| 1327 | prof_tctx_should_destroy(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 1328 | malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 1329 | |||
| 1330 | if (opt_prof_accum) { | ||
| 1331 | return false; | ||
| 1332 | } | ||
| 1333 | if (tctx->cnts.curobjs != 0) { | ||
| 1334 | return false; | ||
| 1335 | } | ||
| 1336 | if (tctx->prepared) { | ||
| 1337 | return false; | ||
| 1338 | } | ||
| 1339 | if (tctx->recent_count != 0) { | ||
| 1340 | return false; | ||
| 1341 | } | ||
| 1342 | return true; | ||
| 1343 | } | ||
| 1344 | |||
| 1345 | static void | ||
| 1346 | prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 1347 | malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 1348 | |||
| 1349 | assert(tctx->cnts.curobjs == 0); | ||
| 1350 | assert(tctx->cnts.curbytes == 0); | ||
| 1351 | /* | ||
| 1352 | * These asserts are not correct -- see the comment about races in | ||
| 1353 | * prof.c | ||
| 1354 | * | ||
| 1355 | * assert(tctx->cnts.curobjs_shifted_unbiased == 0); | ||
| 1356 | * assert(tctx->cnts.curbytes_unbiased == 0); | ||
| 1357 | */ | ||
| 1358 | assert(!opt_prof_accum); | ||
| 1359 | assert(tctx->cnts.accumobjs == 0); | ||
| 1360 | assert(tctx->cnts.accumbytes == 0); | ||
| 1361 | /* | ||
| 1362 | * These ones are, since accumbyte counts never go down. Either | ||
| 1363 | * prof_accum is off (in which case these should never have changed from | ||
| 1364 | * their initial value of zero), or it's on (in which case we shouldn't | ||
| 1365 | * be destroying this tctx). | ||
| 1366 | */ | ||
| 1367 | assert(tctx->cnts.accumobjs_shifted_unbiased == 0); | ||
| 1368 | assert(tctx->cnts.accumbytes_unbiased == 0); | ||
| 1369 | |||
| 1370 | prof_gctx_t *gctx = tctx->gctx; | ||
| 1371 | |||
| 1372 | { | ||
| 1373 | prof_tdata_t *tdata = tctx->tdata; | ||
| 1374 | tctx->tdata = NULL; | ||
| 1375 | ckh_remove(tsd, &tdata->bt2tctx, &gctx->bt, NULL, NULL); | ||
| 1376 | bool destroy_tdata = prof_tdata_should_destroy(tsd_tsdn(tsd), | ||
| 1377 | tdata, false); | ||
| 1378 | malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock); | ||
| 1379 | if (destroy_tdata) { | ||
| 1380 | prof_tdata_destroy(tsd, tdata, false); | ||
| 1381 | } | ||
| 1382 | } | ||
| 1383 | |||
| 1384 | bool destroy_tctx, destroy_gctx; | ||
| 1385 | |||
| 1386 | malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock); | ||
| 1387 | switch (tctx->state) { | ||
| 1388 | case prof_tctx_state_nominal: | ||
| 1389 | tctx_tree_remove(&gctx->tctxs, tctx); | ||
| 1390 | destroy_tctx = true; | ||
| 1391 | if (prof_gctx_should_destroy(gctx)) { | ||
| 1392 | /* | ||
| 1393 | * Increment gctx->nlimbo in order to keep another | ||
| 1394 | * thread from winning the race to destroy gctx while | ||
| 1395 | * this one has gctx->lock dropped. Without this, it | ||
| 1396 | * would be possible for another thread to: | ||
| 1397 | * | ||
| 1398 | * 1) Sample an allocation associated with gctx. | ||
| 1399 | * 2) Deallocate the sampled object. | ||
| 1400 | * 3) Successfully prof_gctx_try_destroy(gctx). | ||
| 1401 | * | ||
| 1402 | * The result would be that gctx no longer exists by the | ||
| 1403 | * time this thread accesses it in | ||
| 1404 | * prof_gctx_try_destroy(). | ||
| 1405 | */ | ||
| 1406 | gctx->nlimbo++; | ||
| 1407 | destroy_gctx = true; | ||
| 1408 | } else { | ||
| 1409 | destroy_gctx = false; | ||
| 1410 | } | ||
| 1411 | break; | ||
| 1412 | case prof_tctx_state_dumping: | ||
| 1413 | /* | ||
| 1414 | * A dumping thread needs tctx to remain valid until dumping | ||
| 1415 | * has finished. Change state such that the dumping thread will | ||
| 1416 | * complete destruction during a late dump iteration phase. | ||
| 1417 | */ | ||
| 1418 | tctx->state = prof_tctx_state_purgatory; | ||
| 1419 | destroy_tctx = false; | ||
| 1420 | destroy_gctx = false; | ||
| 1421 | break; | ||
| 1422 | default: | ||
| 1423 | not_reached(); | ||
| 1424 | destroy_tctx = false; | ||
| 1425 | destroy_gctx = false; | ||
| 1426 | } | ||
| 1427 | malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock); | ||
| 1428 | if (destroy_gctx) { | ||
| 1429 | prof_gctx_try_destroy(tsd, prof_tdata_get(tsd, false), gctx); | ||
| 1430 | } | ||
| 1431 | if (destroy_tctx) { | ||
| 1432 | idalloctm(tsd_tsdn(tsd), tctx, NULL, NULL, true, true); | ||
| 1433 | } | ||
| 1434 | } | ||
| 1435 | |||
| 1436 | void | ||
| 1437 | prof_tctx_try_destroy(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 1438 | malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 1439 | if (prof_tctx_should_destroy(tsd, tctx)) { | ||
| 1440 | /* tctx->tdata->lock will be released in prof_tctx_destroy(). */ | ||
| 1441 | prof_tctx_destroy(tsd, tctx); | ||
| 1442 | } else { | ||
| 1443 | malloc_mutex_unlock(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 1444 | } | ||
| 1445 | } | ||
| 1446 | |||
| 1447 | /******************************************************************************/ | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/prof_log.c b/examples/redis-unstable/deps/jemalloc/src/prof_log.c deleted file mode 100644 index 0632c3b..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/prof_log.c +++ /dev/null | |||
| @@ -1,717 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/buf_writer.h" | ||
| 6 | #include "jemalloc/internal/ckh.h" | ||
| 7 | #include "jemalloc/internal/emitter.h" | ||
| 8 | #include "jemalloc/internal/hash.h" | ||
| 9 | #include "jemalloc/internal/malloc_io.h" | ||
| 10 | #include "jemalloc/internal/mutex.h" | ||
| 11 | #include "jemalloc/internal/prof_data.h" | ||
| 12 | #include "jemalloc/internal/prof_log.h" | ||
| 13 | #include "jemalloc/internal/prof_sys.h" | ||
| 14 | |||
| 15 | bool opt_prof_log = false; | ||
| 16 | typedef enum prof_logging_state_e prof_logging_state_t; | ||
| 17 | enum prof_logging_state_e { | ||
| 18 | prof_logging_state_stopped, | ||
| 19 | prof_logging_state_started, | ||
| 20 | prof_logging_state_dumping | ||
| 21 | }; | ||
| 22 | |||
| 23 | /* | ||
| 24 | * - stopped: log_start never called, or previous log_stop has completed. | ||
| 25 | * - started: log_start called, log_stop not called yet. Allocations are logged. | ||
| 26 | * - dumping: log_stop called but not finished; samples are not logged anymore. | ||
| 27 | */ | ||
| 28 | prof_logging_state_t prof_logging_state = prof_logging_state_stopped; | ||
| 29 | |||
| 30 | /* Used in unit tests. */ | ||
| 31 | static bool prof_log_dummy = false; | ||
| 32 | |||
| 33 | /* Incremented for every log file that is output. */ | ||
| 34 | static uint64_t log_seq = 0; | ||
| 35 | static char log_filename[ | ||
| 36 | /* Minimize memory bloat for non-prof builds. */ | ||
| 37 | #ifdef JEMALLOC_PROF | ||
| 38 | PATH_MAX + | ||
| 39 | #endif | ||
| 40 | 1]; | ||
| 41 | |||
| 42 | /* Timestamp for most recent call to log_start(). */ | ||
| 43 | static nstime_t log_start_timestamp; | ||
| 44 | |||
| 45 | /* Increment these when adding to the log_bt and log_thr linked lists. */ | ||
| 46 | static size_t log_bt_index = 0; | ||
| 47 | static size_t log_thr_index = 0; | ||
| 48 | |||
| 49 | /* Linked list node definitions. These are only used in this file. */ | ||
| 50 | typedef struct prof_bt_node_s prof_bt_node_t; | ||
| 51 | |||
| 52 | struct prof_bt_node_s { | ||
| 53 | prof_bt_node_t *next; | ||
| 54 | size_t index; | ||
| 55 | prof_bt_t bt; | ||
| 56 | /* Variable size backtrace vector pointed to by bt. */ | ||
| 57 | void *vec[1]; | ||
| 58 | }; | ||
| 59 | |||
| 60 | typedef struct prof_thr_node_s prof_thr_node_t; | ||
| 61 | |||
| 62 | struct prof_thr_node_s { | ||
| 63 | prof_thr_node_t *next; | ||
| 64 | size_t index; | ||
| 65 | uint64_t thr_uid; | ||
| 66 | /* Variable size based on thr_name_sz. */ | ||
| 67 | char name[1]; | ||
| 68 | }; | ||
| 69 | |||
| 70 | typedef struct prof_alloc_node_s prof_alloc_node_t; | ||
| 71 | |||
| 72 | /* This is output when logging sampled allocations. */ | ||
| 73 | struct prof_alloc_node_s { | ||
| 74 | prof_alloc_node_t *next; | ||
| 75 | /* Indices into an array of thread data. */ | ||
| 76 | size_t alloc_thr_ind; | ||
| 77 | size_t free_thr_ind; | ||
| 78 | |||
| 79 | /* Indices into an array of backtraces. */ | ||
| 80 | size_t alloc_bt_ind; | ||
| 81 | size_t free_bt_ind; | ||
| 82 | |||
| 83 | uint64_t alloc_time_ns; | ||
| 84 | uint64_t free_time_ns; | ||
| 85 | |||
| 86 | size_t usize; | ||
| 87 | }; | ||
| 88 | |||
| 89 | /* | ||
| 90 | * Created on the first call to prof_try_log and deleted on prof_log_stop. | ||
| 91 | * These are the backtraces and threads that have already been logged by an | ||
| 92 | * allocation. | ||
| 93 | */ | ||
| 94 | static bool log_tables_initialized = false; | ||
| 95 | static ckh_t log_bt_node_set; | ||
| 96 | static ckh_t log_thr_node_set; | ||
| 97 | |||
| 98 | /* Store linked lists for logged data. */ | ||
| 99 | static prof_bt_node_t *log_bt_first = NULL; | ||
| 100 | static prof_bt_node_t *log_bt_last = NULL; | ||
| 101 | static prof_thr_node_t *log_thr_first = NULL; | ||
| 102 | static prof_thr_node_t *log_thr_last = NULL; | ||
| 103 | static prof_alloc_node_t *log_alloc_first = NULL; | ||
| 104 | static prof_alloc_node_t *log_alloc_last = NULL; | ||
| 105 | |||
| 106 | /* Protects the prof_logging_state and any log_{...} variable. */ | ||
| 107 | malloc_mutex_t log_mtx; | ||
| 108 | |||
| 109 | /******************************************************************************/ | ||
| 110 | /* | ||
| 111 | * Function prototypes for static functions that are referenced prior to | ||
| 112 | * definition. | ||
| 113 | */ | ||
| 114 | |||
| 115 | /* Hashtable functions for log_bt_node_set and log_thr_node_set. */ | ||
| 116 | static void prof_thr_node_hash(const void *key, size_t r_hash[2]); | ||
| 117 | static bool prof_thr_node_keycomp(const void *k1, const void *k2); | ||
| 118 | static void prof_bt_node_hash(const void *key, size_t r_hash[2]); | ||
| 119 | static bool prof_bt_node_keycomp(const void *k1, const void *k2); | ||
| 120 | |||
| 121 | /******************************************************************************/ | ||
| 122 | |||
| 123 | static size_t | ||
| 124 | prof_log_bt_index(tsd_t *tsd, prof_bt_t *bt) { | ||
| 125 | assert(prof_logging_state == prof_logging_state_started); | ||
| 126 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &log_mtx); | ||
| 127 | |||
| 128 | prof_bt_node_t dummy_node; | ||
| 129 | dummy_node.bt = *bt; | ||
| 130 | prof_bt_node_t *node; | ||
| 131 | |||
| 132 | /* See if this backtrace is already cached in the table. */ | ||
| 133 | if (ckh_search(&log_bt_node_set, (void *)(&dummy_node), | ||
| 134 | (void **)(&node), NULL)) { | ||
| 135 | size_t sz = offsetof(prof_bt_node_t, vec) + | ||
| 136 | (bt->len * sizeof(void *)); | ||
| 137 | prof_bt_node_t *new_node = (prof_bt_node_t *) | ||
| 138 | iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, | ||
| 139 | true, arena_get(TSDN_NULL, 0, true), true); | ||
| 140 | if (log_bt_first == NULL) { | ||
| 141 | log_bt_first = new_node; | ||
| 142 | log_bt_last = new_node; | ||
| 143 | } else { | ||
| 144 | log_bt_last->next = new_node; | ||
| 145 | log_bt_last = new_node; | ||
| 146 | } | ||
| 147 | |||
| 148 | new_node->next = NULL; | ||
| 149 | new_node->index = log_bt_index; | ||
| 150 | /* | ||
| 151 | * Copy the backtrace: bt is inside a tdata or gctx, which | ||
| 152 | * might die before prof_log_stop is called. | ||
| 153 | */ | ||
| 154 | new_node->bt.len = bt->len; | ||
| 155 | memcpy(new_node->vec, bt->vec, bt->len * sizeof(void *)); | ||
| 156 | new_node->bt.vec = new_node->vec; | ||
| 157 | |||
| 158 | log_bt_index++; | ||
| 159 | ckh_insert(tsd, &log_bt_node_set, (void *)new_node, NULL); | ||
| 160 | return new_node->index; | ||
| 161 | } else { | ||
| 162 | return node->index; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | static size_t | ||
| 167 | prof_log_thr_index(tsd_t *tsd, uint64_t thr_uid, const char *name) { | ||
| 168 | assert(prof_logging_state == prof_logging_state_started); | ||
| 169 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &log_mtx); | ||
| 170 | |||
| 171 | prof_thr_node_t dummy_node; | ||
| 172 | dummy_node.thr_uid = thr_uid; | ||
| 173 | prof_thr_node_t *node; | ||
| 174 | |||
| 175 | /* See if this thread is already cached in the table. */ | ||
| 176 | if (ckh_search(&log_thr_node_set, (void *)(&dummy_node), | ||
| 177 | (void **)(&node), NULL)) { | ||
| 178 | size_t sz = offsetof(prof_thr_node_t, name) + strlen(name) + 1; | ||
| 179 | prof_thr_node_t *new_node = (prof_thr_node_t *) | ||
| 180 | iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, | ||
| 181 | true, arena_get(TSDN_NULL, 0, true), true); | ||
| 182 | if (log_thr_first == NULL) { | ||
| 183 | log_thr_first = new_node; | ||
| 184 | log_thr_last = new_node; | ||
| 185 | } else { | ||
| 186 | log_thr_last->next = new_node; | ||
| 187 | log_thr_last = new_node; | ||
| 188 | } | ||
| 189 | |||
| 190 | new_node->next = NULL; | ||
| 191 | new_node->index = log_thr_index; | ||
| 192 | new_node->thr_uid = thr_uid; | ||
| 193 | strcpy(new_node->name, name); | ||
| 194 | |||
| 195 | log_thr_index++; | ||
| 196 | ckh_insert(tsd, &log_thr_node_set, (void *)new_node, NULL); | ||
| 197 | return new_node->index; | ||
| 198 | } else { | ||
| 199 | return node->index; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | JEMALLOC_COLD | ||
| 204 | void | ||
| 205 | prof_try_log(tsd_t *tsd, size_t usize, prof_info_t *prof_info) { | ||
| 206 | cassert(config_prof); | ||
| 207 | prof_tctx_t *tctx = prof_info->alloc_tctx; | ||
| 208 | malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 209 | |||
| 210 | prof_tdata_t *cons_tdata = prof_tdata_get(tsd, false); | ||
| 211 | if (cons_tdata == NULL) { | ||
| 212 | /* | ||
| 213 | * We decide not to log these allocations. cons_tdata will be | ||
| 214 | * NULL only when the current thread is in a weird state (e.g. | ||
| 215 | * it's being destroyed). | ||
| 216 | */ | ||
| 217 | return; | ||
| 218 | } | ||
| 219 | |||
| 220 | malloc_mutex_lock(tsd_tsdn(tsd), &log_mtx); | ||
| 221 | |||
| 222 | if (prof_logging_state != prof_logging_state_started) { | ||
| 223 | goto label_done; | ||
| 224 | } | ||
| 225 | |||
| 226 | if (!log_tables_initialized) { | ||
| 227 | bool err1 = ckh_new(tsd, &log_bt_node_set, PROF_CKH_MINITEMS, | ||
| 228 | prof_bt_node_hash, prof_bt_node_keycomp); | ||
| 229 | bool err2 = ckh_new(tsd, &log_thr_node_set, PROF_CKH_MINITEMS, | ||
| 230 | prof_thr_node_hash, prof_thr_node_keycomp); | ||
| 231 | if (err1 || err2) { | ||
| 232 | goto label_done; | ||
| 233 | } | ||
| 234 | log_tables_initialized = true; | ||
| 235 | } | ||
| 236 | |||
| 237 | nstime_t alloc_time = prof_info->alloc_time; | ||
| 238 | nstime_t free_time; | ||
| 239 | nstime_prof_init_update(&free_time); | ||
| 240 | |||
| 241 | size_t sz = sizeof(prof_alloc_node_t); | ||
| 242 | prof_alloc_node_t *new_node = (prof_alloc_node_t *) | ||
| 243 | iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, true, | ||
| 244 | arena_get(TSDN_NULL, 0, true), true); | ||
| 245 | |||
| 246 | const char *prod_thr_name = (tctx->tdata->thread_name == NULL)? | ||
| 247 | "" : tctx->tdata->thread_name; | ||
| 248 | const char *cons_thr_name = prof_thread_name_get(tsd); | ||
| 249 | |||
| 250 | prof_bt_t bt; | ||
| 251 | /* Initialize the backtrace, using the buffer in tdata to store it. */ | ||
| 252 | bt_init(&bt, cons_tdata->vec); | ||
| 253 | prof_backtrace(tsd, &bt); | ||
| 254 | prof_bt_t *cons_bt = &bt; | ||
| 255 | |||
| 256 | /* We haven't destroyed tctx yet, so gctx should be good to read. */ | ||
| 257 | prof_bt_t *prod_bt = &tctx->gctx->bt; | ||
| 258 | |||
| 259 | new_node->next = NULL; | ||
| 260 | new_node->alloc_thr_ind = prof_log_thr_index(tsd, tctx->tdata->thr_uid, | ||
| 261 | prod_thr_name); | ||
| 262 | new_node->free_thr_ind = prof_log_thr_index(tsd, cons_tdata->thr_uid, | ||
| 263 | cons_thr_name); | ||
| 264 | new_node->alloc_bt_ind = prof_log_bt_index(tsd, prod_bt); | ||
| 265 | new_node->free_bt_ind = prof_log_bt_index(tsd, cons_bt); | ||
| 266 | new_node->alloc_time_ns = nstime_ns(&alloc_time); | ||
| 267 | new_node->free_time_ns = nstime_ns(&free_time); | ||
| 268 | new_node->usize = usize; | ||
| 269 | |||
| 270 | if (log_alloc_first == NULL) { | ||
| 271 | log_alloc_first = new_node; | ||
| 272 | log_alloc_last = new_node; | ||
| 273 | } else { | ||
| 274 | log_alloc_last->next = new_node; | ||
| 275 | log_alloc_last = new_node; | ||
| 276 | } | ||
| 277 | |||
| 278 | label_done: | ||
| 279 | malloc_mutex_unlock(tsd_tsdn(tsd), &log_mtx); | ||
| 280 | } | ||
| 281 | |||
| 282 | static void | ||
| 283 | prof_bt_node_hash(const void *key, size_t r_hash[2]) { | ||
| 284 | const prof_bt_node_t *bt_node = (prof_bt_node_t *)key; | ||
| 285 | prof_bt_hash((void *)(&bt_node->bt), r_hash); | ||
| 286 | } | ||
| 287 | |||
| 288 | static bool | ||
| 289 | prof_bt_node_keycomp(const void *k1, const void *k2) { | ||
| 290 | const prof_bt_node_t *bt_node1 = (prof_bt_node_t *)k1; | ||
| 291 | const prof_bt_node_t *bt_node2 = (prof_bt_node_t *)k2; | ||
| 292 | return prof_bt_keycomp((void *)(&bt_node1->bt), | ||
| 293 | (void *)(&bt_node2->bt)); | ||
| 294 | } | ||
| 295 | |||
| 296 | static void | ||
| 297 | prof_thr_node_hash(const void *key, size_t r_hash[2]) { | ||
| 298 | const prof_thr_node_t *thr_node = (prof_thr_node_t *)key; | ||
| 299 | hash(&thr_node->thr_uid, sizeof(uint64_t), 0x94122f35U, r_hash); | ||
| 300 | } | ||
| 301 | |||
| 302 | static bool | ||
| 303 | prof_thr_node_keycomp(const void *k1, const void *k2) { | ||
| 304 | const prof_thr_node_t *thr_node1 = (prof_thr_node_t *)k1; | ||
| 305 | const prof_thr_node_t *thr_node2 = (prof_thr_node_t *)k2; | ||
| 306 | return thr_node1->thr_uid == thr_node2->thr_uid; | ||
| 307 | } | ||
| 308 | |||
| 309 | /* Used in unit tests. */ | ||
| 310 | size_t | ||
| 311 | prof_log_bt_count(void) { | ||
| 312 | cassert(config_prof); | ||
| 313 | size_t cnt = 0; | ||
| 314 | prof_bt_node_t *node = log_bt_first; | ||
| 315 | while (node != NULL) { | ||
| 316 | cnt++; | ||
| 317 | node = node->next; | ||
| 318 | } | ||
| 319 | return cnt; | ||
| 320 | } | ||
| 321 | |||
| 322 | /* Used in unit tests. */ | ||
| 323 | size_t | ||
| 324 | prof_log_alloc_count(void) { | ||
| 325 | cassert(config_prof); | ||
| 326 | size_t cnt = 0; | ||
| 327 | prof_alloc_node_t *node = log_alloc_first; | ||
| 328 | while (node != NULL) { | ||
| 329 | cnt++; | ||
| 330 | node = node->next; | ||
| 331 | } | ||
| 332 | return cnt; | ||
| 333 | } | ||
| 334 | |||
| 335 | /* Used in unit tests. */ | ||
| 336 | size_t | ||
| 337 | prof_log_thr_count(void) { | ||
| 338 | cassert(config_prof); | ||
| 339 | size_t cnt = 0; | ||
| 340 | prof_thr_node_t *node = log_thr_first; | ||
| 341 | while (node != NULL) { | ||
| 342 | cnt++; | ||
| 343 | node = node->next; | ||
| 344 | } | ||
| 345 | return cnt; | ||
| 346 | } | ||
| 347 | |||
| 348 | /* Used in unit tests. */ | ||
| 349 | bool | ||
| 350 | prof_log_is_logging(void) { | ||
| 351 | cassert(config_prof); | ||
| 352 | return prof_logging_state == prof_logging_state_started; | ||
| 353 | } | ||
| 354 | |||
| 355 | /* Used in unit tests. */ | ||
| 356 | bool | ||
| 357 | prof_log_rep_check(void) { | ||
| 358 | cassert(config_prof); | ||
| 359 | if (prof_logging_state == prof_logging_state_stopped | ||
| 360 | && log_tables_initialized) { | ||
| 361 | return true; | ||
| 362 | } | ||
| 363 | |||
| 364 | if (log_bt_last != NULL && log_bt_last->next != NULL) { | ||
| 365 | return true; | ||
| 366 | } | ||
| 367 | if (log_thr_last != NULL && log_thr_last->next != NULL) { | ||
| 368 | return true; | ||
| 369 | } | ||
| 370 | if (log_alloc_last != NULL && log_alloc_last->next != NULL) { | ||
| 371 | return true; | ||
| 372 | } | ||
| 373 | |||
| 374 | size_t bt_count = prof_log_bt_count(); | ||
| 375 | size_t thr_count = prof_log_thr_count(); | ||
| 376 | size_t alloc_count = prof_log_alloc_count(); | ||
| 377 | |||
| 378 | |||
| 379 | if (prof_logging_state == prof_logging_state_stopped) { | ||
| 380 | if (bt_count != 0 || thr_count != 0 || alloc_count || 0) { | ||
| 381 | return true; | ||
| 382 | } | ||
| 383 | } | ||
| 384 | |||
| 385 | prof_alloc_node_t *node = log_alloc_first; | ||
| 386 | while (node != NULL) { | ||
| 387 | if (node->alloc_bt_ind >= bt_count) { | ||
| 388 | return true; | ||
| 389 | } | ||
| 390 | if (node->free_bt_ind >= bt_count) { | ||
| 391 | return true; | ||
| 392 | } | ||
| 393 | if (node->alloc_thr_ind >= thr_count) { | ||
| 394 | return true; | ||
| 395 | } | ||
| 396 | if (node->free_thr_ind >= thr_count) { | ||
| 397 | return true; | ||
| 398 | } | ||
| 399 | if (node->alloc_time_ns > node->free_time_ns) { | ||
| 400 | return true; | ||
| 401 | } | ||
| 402 | node = node->next; | ||
| 403 | } | ||
| 404 | |||
| 405 | return false; | ||
| 406 | } | ||
| 407 | |||
| 408 | /* Used in unit tests. */ | ||
| 409 | void | ||
| 410 | prof_log_dummy_set(bool new_value) { | ||
| 411 | cassert(config_prof); | ||
| 412 | prof_log_dummy = new_value; | ||
| 413 | } | ||
| 414 | |||
| 415 | /* Used as an atexit function to stop logging on exit. */ | ||
| 416 | static void | ||
| 417 | prof_log_stop_final(void) { | ||
| 418 | tsd_t *tsd = tsd_fetch(); | ||
| 419 | prof_log_stop(tsd_tsdn(tsd)); | ||
| 420 | } | ||
| 421 | |||
| 422 | JEMALLOC_COLD | ||
| 423 | bool | ||
| 424 | prof_log_start(tsdn_t *tsdn, const char *filename) { | ||
| 425 | cassert(config_prof); | ||
| 426 | |||
| 427 | if (!opt_prof) { | ||
| 428 | return true; | ||
| 429 | } | ||
| 430 | |||
| 431 | bool ret = false; | ||
| 432 | |||
| 433 | malloc_mutex_lock(tsdn, &log_mtx); | ||
| 434 | |||
| 435 | static bool prof_log_atexit_called = false; | ||
| 436 | if (!prof_log_atexit_called) { | ||
| 437 | prof_log_atexit_called = true; | ||
| 438 | if (atexit(prof_log_stop_final) != 0) { | ||
| 439 | malloc_write("<jemalloc>: Error in atexit() " | ||
| 440 | "for logging\n"); | ||
| 441 | if (opt_abort) { | ||
| 442 | abort(); | ||
| 443 | } | ||
| 444 | ret = true; | ||
| 445 | goto label_done; | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | if (prof_logging_state != prof_logging_state_stopped) { | ||
| 450 | ret = true; | ||
| 451 | } else if (filename == NULL) { | ||
| 452 | /* Make default name. */ | ||
| 453 | prof_get_default_filename(tsdn, log_filename, log_seq); | ||
| 454 | log_seq++; | ||
| 455 | prof_logging_state = prof_logging_state_started; | ||
| 456 | } else if (strlen(filename) >= PROF_DUMP_FILENAME_LEN) { | ||
| 457 | ret = true; | ||
| 458 | } else { | ||
| 459 | strcpy(log_filename, filename); | ||
| 460 | prof_logging_state = prof_logging_state_started; | ||
| 461 | } | ||
| 462 | |||
| 463 | if (!ret) { | ||
| 464 | nstime_prof_init_update(&log_start_timestamp); | ||
| 465 | } | ||
| 466 | label_done: | ||
| 467 | malloc_mutex_unlock(tsdn, &log_mtx); | ||
| 468 | |||
| 469 | return ret; | ||
| 470 | } | ||
| 471 | |||
| 472 | struct prof_emitter_cb_arg_s { | ||
| 473 | int fd; | ||
| 474 | ssize_t ret; | ||
| 475 | }; | ||
| 476 | |||
| 477 | static void | ||
| 478 | prof_emitter_write_cb(void *opaque, const char *to_write) { | ||
| 479 | struct prof_emitter_cb_arg_s *arg = | ||
| 480 | (struct prof_emitter_cb_arg_s *)opaque; | ||
| 481 | size_t bytes = strlen(to_write); | ||
| 482 | if (prof_log_dummy) { | ||
| 483 | return; | ||
| 484 | } | ||
| 485 | arg->ret = malloc_write_fd(arg->fd, to_write, bytes); | ||
| 486 | } | ||
| 487 | |||
| 488 | /* | ||
| 489 | * prof_log_emit_{...} goes through the appropriate linked list, emitting each | ||
| 490 | * node to the json and deallocating it. | ||
| 491 | */ | ||
| 492 | static void | ||
| 493 | prof_log_emit_threads(tsd_t *tsd, emitter_t *emitter) { | ||
| 494 | emitter_json_array_kv_begin(emitter, "threads"); | ||
| 495 | prof_thr_node_t *thr_node = log_thr_first; | ||
| 496 | prof_thr_node_t *thr_old_node; | ||
| 497 | while (thr_node != NULL) { | ||
| 498 | emitter_json_object_begin(emitter); | ||
| 499 | |||
| 500 | emitter_json_kv(emitter, "thr_uid", emitter_type_uint64, | ||
| 501 | &thr_node->thr_uid); | ||
| 502 | |||
| 503 | char *thr_name = thr_node->name; | ||
| 504 | |||
| 505 | emitter_json_kv(emitter, "thr_name", emitter_type_string, | ||
| 506 | &thr_name); | ||
| 507 | |||
| 508 | emitter_json_object_end(emitter); | ||
| 509 | thr_old_node = thr_node; | ||
| 510 | thr_node = thr_node->next; | ||
| 511 | idalloctm(tsd_tsdn(tsd), thr_old_node, NULL, NULL, true, true); | ||
| 512 | } | ||
| 513 | emitter_json_array_end(emitter); | ||
| 514 | } | ||
| 515 | |||
| 516 | static void | ||
| 517 | prof_log_emit_traces(tsd_t *tsd, emitter_t *emitter) { | ||
| 518 | emitter_json_array_kv_begin(emitter, "stack_traces"); | ||
| 519 | prof_bt_node_t *bt_node = log_bt_first; | ||
| 520 | prof_bt_node_t *bt_old_node; | ||
| 521 | /* | ||
| 522 | * Calculate how many hex digits we need: twice number of bytes, two for | ||
| 523 | * "0x", and then one more for terminating '\0'. | ||
| 524 | */ | ||
| 525 | char buf[2 * sizeof(intptr_t) + 3]; | ||
| 526 | size_t buf_sz = sizeof(buf); | ||
| 527 | while (bt_node != NULL) { | ||
| 528 | emitter_json_array_begin(emitter); | ||
| 529 | size_t i; | ||
| 530 | for (i = 0; i < bt_node->bt.len; i++) { | ||
| 531 | malloc_snprintf(buf, buf_sz, "%p", bt_node->bt.vec[i]); | ||
| 532 | char *trace_str = buf; | ||
| 533 | emitter_json_value(emitter, emitter_type_string, | ||
| 534 | &trace_str); | ||
| 535 | } | ||
| 536 | emitter_json_array_end(emitter); | ||
| 537 | |||
| 538 | bt_old_node = bt_node; | ||
| 539 | bt_node = bt_node->next; | ||
| 540 | idalloctm(tsd_tsdn(tsd), bt_old_node, NULL, NULL, true, true); | ||
| 541 | } | ||
| 542 | emitter_json_array_end(emitter); | ||
| 543 | } | ||
| 544 | |||
| 545 | static void | ||
| 546 | prof_log_emit_allocs(tsd_t *tsd, emitter_t *emitter) { | ||
| 547 | emitter_json_array_kv_begin(emitter, "allocations"); | ||
| 548 | prof_alloc_node_t *alloc_node = log_alloc_first; | ||
| 549 | prof_alloc_node_t *alloc_old_node; | ||
| 550 | while (alloc_node != NULL) { | ||
| 551 | emitter_json_object_begin(emitter); | ||
| 552 | |||
| 553 | emitter_json_kv(emitter, "alloc_thread", emitter_type_size, | ||
| 554 | &alloc_node->alloc_thr_ind); | ||
| 555 | |||
| 556 | emitter_json_kv(emitter, "free_thread", emitter_type_size, | ||
| 557 | &alloc_node->free_thr_ind); | ||
| 558 | |||
| 559 | emitter_json_kv(emitter, "alloc_trace", emitter_type_size, | ||
| 560 | &alloc_node->alloc_bt_ind); | ||
| 561 | |||
| 562 | emitter_json_kv(emitter, "free_trace", emitter_type_size, | ||
| 563 | &alloc_node->free_bt_ind); | ||
| 564 | |||
| 565 | emitter_json_kv(emitter, "alloc_timestamp", | ||
| 566 | emitter_type_uint64, &alloc_node->alloc_time_ns); | ||
| 567 | |||
| 568 | emitter_json_kv(emitter, "free_timestamp", emitter_type_uint64, | ||
| 569 | &alloc_node->free_time_ns); | ||
| 570 | |||
| 571 | emitter_json_kv(emitter, "usize", emitter_type_uint64, | ||
| 572 | &alloc_node->usize); | ||
| 573 | |||
| 574 | emitter_json_object_end(emitter); | ||
| 575 | |||
| 576 | alloc_old_node = alloc_node; | ||
| 577 | alloc_node = alloc_node->next; | ||
| 578 | idalloctm(tsd_tsdn(tsd), alloc_old_node, NULL, NULL, true, | ||
| 579 | true); | ||
| 580 | } | ||
| 581 | emitter_json_array_end(emitter); | ||
| 582 | } | ||
| 583 | |||
| 584 | static void | ||
| 585 | prof_log_emit_metadata(emitter_t *emitter) { | ||
| 586 | emitter_json_object_kv_begin(emitter, "info"); | ||
| 587 | |||
| 588 | nstime_t now; | ||
| 589 | |||
| 590 | nstime_prof_init_update(&now); | ||
| 591 | uint64_t ns = nstime_ns(&now) - nstime_ns(&log_start_timestamp); | ||
| 592 | emitter_json_kv(emitter, "duration", emitter_type_uint64, &ns); | ||
| 593 | |||
| 594 | char *vers = JEMALLOC_VERSION; | ||
| 595 | emitter_json_kv(emitter, "version", | ||
| 596 | emitter_type_string, &vers); | ||
| 597 | |||
| 598 | emitter_json_kv(emitter, "lg_sample_rate", | ||
| 599 | emitter_type_int, &lg_prof_sample); | ||
| 600 | |||
| 601 | const char *res_type = prof_time_res_mode_names[opt_prof_time_res]; | ||
| 602 | emitter_json_kv(emitter, "prof_time_resolution", emitter_type_string, | ||
| 603 | &res_type); | ||
| 604 | |||
| 605 | int pid = prof_getpid(); | ||
| 606 | emitter_json_kv(emitter, "pid", emitter_type_int, &pid); | ||
| 607 | |||
| 608 | emitter_json_object_end(emitter); | ||
| 609 | } | ||
| 610 | |||
| 611 | #define PROF_LOG_STOP_BUFSIZE PROF_DUMP_BUFSIZE | ||
| 612 | JEMALLOC_COLD | ||
| 613 | bool | ||
| 614 | prof_log_stop(tsdn_t *tsdn) { | ||
| 615 | cassert(config_prof); | ||
| 616 | if (!opt_prof || !prof_booted) { | ||
| 617 | return true; | ||
| 618 | } | ||
| 619 | |||
| 620 | tsd_t *tsd = tsdn_tsd(tsdn); | ||
| 621 | malloc_mutex_lock(tsdn, &log_mtx); | ||
| 622 | |||
| 623 | if (prof_logging_state != prof_logging_state_started) { | ||
| 624 | malloc_mutex_unlock(tsdn, &log_mtx); | ||
| 625 | return true; | ||
| 626 | } | ||
| 627 | |||
| 628 | /* | ||
| 629 | * Set the state to dumping. We'll set it to stopped when we're done. | ||
| 630 | * Since other threads won't be able to start/stop/log when the state is | ||
| 631 | * dumping, we don't have to hold the lock during the whole method. | ||
| 632 | */ | ||
| 633 | prof_logging_state = prof_logging_state_dumping; | ||
| 634 | malloc_mutex_unlock(tsdn, &log_mtx); | ||
| 635 | |||
| 636 | |||
| 637 | emitter_t emitter; | ||
| 638 | |||
| 639 | /* Create a file. */ | ||
| 640 | |||
| 641 | int fd; | ||
| 642 | if (prof_log_dummy) { | ||
| 643 | fd = 0; | ||
| 644 | } else { | ||
| 645 | fd = creat(log_filename, 0644); | ||
| 646 | } | ||
| 647 | |||
| 648 | if (fd == -1) { | ||
| 649 | malloc_printf("<jemalloc>: creat() for log file \"%s\" " | ||
| 650 | " failed with %d\n", log_filename, errno); | ||
| 651 | if (opt_abort) { | ||
| 652 | abort(); | ||
| 653 | } | ||
| 654 | return true; | ||
| 655 | } | ||
| 656 | |||
| 657 | struct prof_emitter_cb_arg_s arg; | ||
| 658 | arg.fd = fd; | ||
| 659 | |||
| 660 | buf_writer_t buf_writer; | ||
| 661 | buf_writer_init(tsdn, &buf_writer, prof_emitter_write_cb, &arg, NULL, | ||
| 662 | PROF_LOG_STOP_BUFSIZE); | ||
| 663 | emitter_init(&emitter, emitter_output_json_compact, buf_writer_cb, | ||
| 664 | &buf_writer); | ||
| 665 | |||
| 666 | emitter_begin(&emitter); | ||
| 667 | prof_log_emit_metadata(&emitter); | ||
| 668 | prof_log_emit_threads(tsd, &emitter); | ||
| 669 | prof_log_emit_traces(tsd, &emitter); | ||
| 670 | prof_log_emit_allocs(tsd, &emitter); | ||
| 671 | emitter_end(&emitter); | ||
| 672 | |||
| 673 | buf_writer_terminate(tsdn, &buf_writer); | ||
| 674 | |||
| 675 | /* Reset global state. */ | ||
| 676 | if (log_tables_initialized) { | ||
| 677 | ckh_delete(tsd, &log_bt_node_set); | ||
| 678 | ckh_delete(tsd, &log_thr_node_set); | ||
| 679 | } | ||
| 680 | log_tables_initialized = false; | ||
| 681 | log_bt_index = 0; | ||
| 682 | log_thr_index = 0; | ||
| 683 | log_bt_first = NULL; | ||
| 684 | log_bt_last = NULL; | ||
| 685 | log_thr_first = NULL; | ||
| 686 | log_thr_last = NULL; | ||
| 687 | log_alloc_first = NULL; | ||
| 688 | log_alloc_last = NULL; | ||
| 689 | |||
| 690 | malloc_mutex_lock(tsdn, &log_mtx); | ||
| 691 | prof_logging_state = prof_logging_state_stopped; | ||
| 692 | malloc_mutex_unlock(tsdn, &log_mtx); | ||
| 693 | |||
| 694 | if (prof_log_dummy) { | ||
| 695 | return false; | ||
| 696 | } | ||
| 697 | return close(fd) || arg.ret == -1; | ||
| 698 | } | ||
| 699 | #undef PROF_LOG_STOP_BUFSIZE | ||
| 700 | |||
| 701 | JEMALLOC_COLD | ||
| 702 | bool | ||
| 703 | prof_log_init(tsd_t *tsd) { | ||
| 704 | cassert(config_prof); | ||
| 705 | if (malloc_mutex_init(&log_mtx, "prof_log", | ||
| 706 | WITNESS_RANK_PROF_LOG, malloc_mutex_rank_exclusive)) { | ||
| 707 | return true; | ||
| 708 | } | ||
| 709 | |||
| 710 | if (opt_prof_log) { | ||
| 711 | prof_log_start(tsd_tsdn(tsd), NULL); | ||
| 712 | } | ||
| 713 | |||
| 714 | return false; | ||
| 715 | } | ||
| 716 | |||
| 717 | /******************************************************************************/ | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/prof_recent.c b/examples/redis-unstable/deps/jemalloc/src/prof_recent.c deleted file mode 100644 index 834a944..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/prof_recent.c +++ /dev/null | |||
| @@ -1,600 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/buf_writer.h" | ||
| 6 | #include "jemalloc/internal/emitter.h" | ||
| 7 | #include "jemalloc/internal/prof_data.h" | ||
| 8 | #include "jemalloc/internal/prof_recent.h" | ||
| 9 | |||
| 10 | ssize_t opt_prof_recent_alloc_max = PROF_RECENT_ALLOC_MAX_DEFAULT; | ||
| 11 | malloc_mutex_t prof_recent_alloc_mtx; /* Protects the fields below */ | ||
| 12 | static atomic_zd_t prof_recent_alloc_max; | ||
| 13 | static ssize_t prof_recent_alloc_count = 0; | ||
| 14 | prof_recent_list_t prof_recent_alloc_list; | ||
| 15 | |||
| 16 | malloc_mutex_t prof_recent_dump_mtx; /* Protects dumping. */ | ||
| 17 | |||
| 18 | static void | ||
| 19 | prof_recent_alloc_max_init() { | ||
| 20 | atomic_store_zd(&prof_recent_alloc_max, opt_prof_recent_alloc_max, | ||
| 21 | ATOMIC_RELAXED); | ||
| 22 | } | ||
| 23 | |||
| 24 | static inline ssize_t | ||
| 25 | prof_recent_alloc_max_get_no_lock() { | ||
| 26 | return atomic_load_zd(&prof_recent_alloc_max, ATOMIC_RELAXED); | ||
| 27 | } | ||
| 28 | |||
| 29 | static inline ssize_t | ||
| 30 | prof_recent_alloc_max_get(tsd_t *tsd) { | ||
| 31 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 32 | return prof_recent_alloc_max_get_no_lock(); | ||
| 33 | } | ||
| 34 | |||
| 35 | static inline ssize_t | ||
| 36 | prof_recent_alloc_max_update(tsd_t *tsd, ssize_t max) { | ||
| 37 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 38 | ssize_t old_max = prof_recent_alloc_max_get(tsd); | ||
| 39 | atomic_store_zd(&prof_recent_alloc_max, max, ATOMIC_RELAXED); | ||
| 40 | return old_max; | ||
| 41 | } | ||
| 42 | |||
| 43 | static prof_recent_t * | ||
| 44 | prof_recent_allocate_node(tsdn_t *tsdn) { | ||
| 45 | return (prof_recent_t *)iallocztm(tsdn, sizeof(prof_recent_t), | ||
| 46 | sz_size2index(sizeof(prof_recent_t)), false, NULL, true, | ||
| 47 | arena_get(tsdn, 0, false), true); | ||
| 48 | } | ||
| 49 | |||
| 50 | static void | ||
| 51 | prof_recent_free_node(tsdn_t *tsdn, prof_recent_t *node) { | ||
| 52 | assert(node != NULL); | ||
| 53 | assert(isalloc(tsdn, node) == sz_s2u(sizeof(prof_recent_t))); | ||
| 54 | idalloctm(tsdn, node, NULL, NULL, true, true); | ||
| 55 | } | ||
| 56 | |||
| 57 | static inline void | ||
| 58 | increment_recent_count(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 59 | malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 60 | ++tctx->recent_count; | ||
| 61 | assert(tctx->recent_count > 0); | ||
| 62 | } | ||
| 63 | |||
| 64 | bool | ||
| 65 | prof_recent_alloc_prepare(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 66 | cassert(config_prof); | ||
| 67 | assert(opt_prof && prof_booted); | ||
| 68 | malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 69 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 70 | |||
| 71 | /* | ||
| 72 | * Check whether last-N mode is turned on without trying to acquire the | ||
| 73 | * lock, so as to optimize for the following two scenarios: | ||
| 74 | * (1) Last-N mode is switched off; | ||
| 75 | * (2) Dumping, during which last-N mode is temporarily turned off so | ||
| 76 | * as not to block sampled allocations. | ||
| 77 | */ | ||
| 78 | if (prof_recent_alloc_max_get_no_lock() == 0) { | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | |||
| 82 | /* | ||
| 83 | * Increment recent_count to hold the tctx so that it won't be gone | ||
| 84 | * even after tctx->tdata->lock is released. This acts as a | ||
| 85 | * "placeholder"; the real recording of the allocation requires a lock | ||
| 86 | * on prof_recent_alloc_mtx and is done in prof_recent_alloc (when | ||
| 87 | * tctx->tdata->lock has been released). | ||
| 88 | */ | ||
| 89 | increment_recent_count(tsd, tctx); | ||
| 90 | return true; | ||
| 91 | } | ||
| 92 | |||
| 93 | static void | ||
| 94 | decrement_recent_count(tsd_t *tsd, prof_tctx_t *tctx) { | ||
| 95 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 96 | assert(tctx != NULL); | ||
| 97 | malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 98 | assert(tctx->recent_count > 0); | ||
| 99 | --tctx->recent_count; | ||
| 100 | prof_tctx_try_destroy(tsd, tctx); | ||
| 101 | } | ||
| 102 | |||
| 103 | static inline edata_t * | ||
| 104 | prof_recent_alloc_edata_get_no_lock(const prof_recent_t *n) { | ||
| 105 | return (edata_t *)atomic_load_p(&n->alloc_edata, ATOMIC_ACQUIRE); | ||
| 106 | } | ||
| 107 | |||
| 108 | edata_t * | ||
| 109 | prof_recent_alloc_edata_get_no_lock_test(const prof_recent_t *n) { | ||
| 110 | cassert(config_prof); | ||
| 111 | return prof_recent_alloc_edata_get_no_lock(n); | ||
| 112 | } | ||
| 113 | |||
| 114 | static inline edata_t * | ||
| 115 | prof_recent_alloc_edata_get(tsd_t *tsd, const prof_recent_t *n) { | ||
| 116 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 117 | return prof_recent_alloc_edata_get_no_lock(n); | ||
| 118 | } | ||
| 119 | |||
| 120 | static void | ||
| 121 | prof_recent_alloc_edata_set(tsd_t *tsd, prof_recent_t *n, edata_t *edata) { | ||
| 122 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 123 | atomic_store_p(&n->alloc_edata, edata, ATOMIC_RELEASE); | ||
| 124 | } | ||
| 125 | |||
| 126 | void | ||
| 127 | edata_prof_recent_alloc_init(edata_t *edata) { | ||
| 128 | cassert(config_prof); | ||
| 129 | edata_prof_recent_alloc_set_dont_call_directly(edata, NULL); | ||
| 130 | } | ||
| 131 | |||
| 132 | static inline prof_recent_t * | ||
| 133 | edata_prof_recent_alloc_get_no_lock(const edata_t *edata) { | ||
| 134 | cassert(config_prof); | ||
| 135 | return edata_prof_recent_alloc_get_dont_call_directly(edata); | ||
| 136 | } | ||
| 137 | |||
| 138 | prof_recent_t * | ||
| 139 | edata_prof_recent_alloc_get_no_lock_test(const edata_t *edata) { | ||
| 140 | cassert(config_prof); | ||
| 141 | return edata_prof_recent_alloc_get_no_lock(edata); | ||
| 142 | } | ||
| 143 | |||
| 144 | static inline prof_recent_t * | ||
| 145 | edata_prof_recent_alloc_get(tsd_t *tsd, const edata_t *edata) { | ||
| 146 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 147 | prof_recent_t *recent_alloc = | ||
| 148 | edata_prof_recent_alloc_get_no_lock(edata); | ||
| 149 | assert(recent_alloc == NULL || | ||
| 150 | prof_recent_alloc_edata_get(tsd, recent_alloc) == edata); | ||
| 151 | return recent_alloc; | ||
| 152 | } | ||
| 153 | |||
| 154 | static prof_recent_t * | ||
| 155 | edata_prof_recent_alloc_update_internal(tsd_t *tsd, edata_t *edata, | ||
| 156 | prof_recent_t *recent_alloc) { | ||
| 157 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 158 | prof_recent_t *old_recent_alloc = | ||
| 159 | edata_prof_recent_alloc_get(tsd, edata); | ||
| 160 | edata_prof_recent_alloc_set_dont_call_directly(edata, recent_alloc); | ||
| 161 | return old_recent_alloc; | ||
| 162 | } | ||
| 163 | |||
| 164 | static void | ||
| 165 | edata_prof_recent_alloc_set(tsd_t *tsd, edata_t *edata, | ||
| 166 | prof_recent_t *recent_alloc) { | ||
| 167 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 168 | assert(recent_alloc != NULL); | ||
| 169 | prof_recent_t *old_recent_alloc = | ||
| 170 | edata_prof_recent_alloc_update_internal(tsd, edata, recent_alloc); | ||
| 171 | assert(old_recent_alloc == NULL); | ||
| 172 | prof_recent_alloc_edata_set(tsd, recent_alloc, edata); | ||
| 173 | } | ||
| 174 | |||
| 175 | static void | ||
| 176 | edata_prof_recent_alloc_reset(tsd_t *tsd, edata_t *edata, | ||
| 177 | prof_recent_t *recent_alloc) { | ||
| 178 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 179 | assert(recent_alloc != NULL); | ||
| 180 | prof_recent_t *old_recent_alloc = | ||
| 181 | edata_prof_recent_alloc_update_internal(tsd, edata, NULL); | ||
| 182 | assert(old_recent_alloc == recent_alloc); | ||
| 183 | assert(edata == prof_recent_alloc_edata_get(tsd, recent_alloc)); | ||
| 184 | prof_recent_alloc_edata_set(tsd, recent_alloc, NULL); | ||
| 185 | } | ||
| 186 | |||
| 187 | /* | ||
| 188 | * This function should be called right before an allocation is released, so | ||
| 189 | * that the associated recent allocation record can contain the following | ||
| 190 | * information: | ||
| 191 | * (1) The allocation is released; | ||
| 192 | * (2) The time of the deallocation; and | ||
| 193 | * (3) The prof_tctx associated with the deallocation. | ||
| 194 | */ | ||
| 195 | void | ||
| 196 | prof_recent_alloc_reset(tsd_t *tsd, edata_t *edata) { | ||
| 197 | cassert(config_prof); | ||
| 198 | /* | ||
| 199 | * Check whether the recent allocation record still exists without | ||
| 200 | * trying to acquire the lock. | ||
| 201 | */ | ||
| 202 | if (edata_prof_recent_alloc_get_no_lock(edata) == NULL) { | ||
| 203 | return; | ||
| 204 | } | ||
| 205 | |||
| 206 | prof_tctx_t *dalloc_tctx = prof_tctx_create(tsd); | ||
| 207 | /* | ||
| 208 | * In case dalloc_tctx is NULL, e.g. due to OOM, we will not record the | ||
| 209 | * deallocation time / tctx, which is handled later, after we check | ||
| 210 | * again when holding the lock. | ||
| 211 | */ | ||
| 212 | |||
| 213 | if (dalloc_tctx != NULL) { | ||
| 214 | malloc_mutex_lock(tsd_tsdn(tsd), dalloc_tctx->tdata->lock); | ||
| 215 | increment_recent_count(tsd, dalloc_tctx); | ||
| 216 | dalloc_tctx->prepared = false; | ||
| 217 | malloc_mutex_unlock(tsd_tsdn(tsd), dalloc_tctx->tdata->lock); | ||
| 218 | } | ||
| 219 | |||
| 220 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 221 | /* Check again after acquiring the lock. */ | ||
| 222 | prof_recent_t *recent = edata_prof_recent_alloc_get(tsd, edata); | ||
| 223 | if (recent != NULL) { | ||
| 224 | assert(nstime_equals_zero(&recent->dalloc_time)); | ||
| 225 | assert(recent->dalloc_tctx == NULL); | ||
| 226 | if (dalloc_tctx != NULL) { | ||
| 227 | nstime_prof_update(&recent->dalloc_time); | ||
| 228 | recent->dalloc_tctx = dalloc_tctx; | ||
| 229 | dalloc_tctx = NULL; | ||
| 230 | } | ||
| 231 | edata_prof_recent_alloc_reset(tsd, edata, recent); | ||
| 232 | } | ||
| 233 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 234 | |||
| 235 | if (dalloc_tctx != NULL) { | ||
| 236 | /* We lost the rase - the allocation record was just gone. */ | ||
| 237 | decrement_recent_count(tsd, dalloc_tctx); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | static void | ||
| 242 | prof_recent_alloc_evict_edata(tsd_t *tsd, prof_recent_t *recent_alloc) { | ||
| 243 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 244 | edata_t *edata = prof_recent_alloc_edata_get(tsd, recent_alloc); | ||
| 245 | if (edata != NULL) { | ||
| 246 | edata_prof_recent_alloc_reset(tsd, edata, recent_alloc); | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | static bool | ||
| 251 | prof_recent_alloc_is_empty(tsd_t *tsd) { | ||
| 252 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 253 | if (ql_empty(&prof_recent_alloc_list)) { | ||
| 254 | assert(prof_recent_alloc_count == 0); | ||
| 255 | return true; | ||
| 256 | } else { | ||
| 257 | assert(prof_recent_alloc_count > 0); | ||
| 258 | return false; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | static void | ||
| 263 | prof_recent_alloc_assert_count(tsd_t *tsd) { | ||
| 264 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 265 | if (!config_debug) { | ||
| 266 | return; | ||
| 267 | } | ||
| 268 | ssize_t count = 0; | ||
| 269 | prof_recent_t *n; | ||
| 270 | ql_foreach(n, &prof_recent_alloc_list, link) { | ||
| 271 | ++count; | ||
| 272 | } | ||
| 273 | assert(count == prof_recent_alloc_count); | ||
| 274 | assert(prof_recent_alloc_max_get(tsd) == -1 || | ||
| 275 | count <= prof_recent_alloc_max_get(tsd)); | ||
| 276 | } | ||
| 277 | |||
| 278 | void | ||
| 279 | prof_recent_alloc(tsd_t *tsd, edata_t *edata, size_t size, size_t usize) { | ||
| 280 | cassert(config_prof); | ||
| 281 | assert(edata != NULL); | ||
| 282 | prof_tctx_t *tctx = edata_prof_tctx_get(edata); | ||
| 283 | |||
| 284 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), tctx->tdata->lock); | ||
| 285 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 286 | prof_recent_alloc_assert_count(tsd); | ||
| 287 | |||
| 288 | /* | ||
| 289 | * Reserve a new prof_recent_t node if needed. If needed, we release | ||
| 290 | * the prof_recent_alloc_mtx lock and allocate. Then, rather than | ||
| 291 | * immediately checking for OOM, we regain the lock and try to make use | ||
| 292 | * of the reserve node if needed. There are six scenarios: | ||
| 293 | * | ||
| 294 | * \ now | no need | need but OOMed | need and allocated | ||
| 295 | * later \ | | | | ||
| 296 | * ------------------------------------------------------------ | ||
| 297 | * no need | (1) | (2) | (3) | ||
| 298 | * ------------------------------------------------------------ | ||
| 299 | * need | (4) | (5) | (6) | ||
| 300 | * | ||
| 301 | * First, "(4)" never happens, because we don't release the lock in the | ||
| 302 | * middle if there's no need for a new node; in such cases "(1)" always | ||
| 303 | * takes place, which is trivial. | ||
| 304 | * | ||
| 305 | * Out of the remaining four scenarios, "(6)" is the common case and is | ||
| 306 | * trivial. "(5)" is also trivial, in which case we'll rollback the | ||
| 307 | * effect of prof_recent_alloc_prepare() as expected. | ||
| 308 | * | ||
| 309 | * "(2)" / "(3)" occurs when the need for a new node is gone after we | ||
| 310 | * regain the lock. If the new node is successfully allocated, i.e. in | ||
| 311 | * the case of "(3)", we'll release it in the end; otherwise, i.e. in | ||
| 312 | * the case of "(2)", we do nothing - we're lucky that the OOM ends up | ||
| 313 | * doing no harm at all. | ||
| 314 | * | ||
| 315 | * Therefore, the only performance cost of the "release lock" -> | ||
| 316 | * "allocate" -> "regain lock" design is the "(3)" case, but it happens | ||
| 317 | * very rarely, so the cost is relatively small compared to the gain of | ||
| 318 | * not having to have the lock order of prof_recent_alloc_mtx above all | ||
| 319 | * the allocation locks. | ||
| 320 | */ | ||
| 321 | prof_recent_t *reserve = NULL; | ||
| 322 | if (prof_recent_alloc_max_get(tsd) == -1 || | ||
| 323 | prof_recent_alloc_count < prof_recent_alloc_max_get(tsd)) { | ||
| 324 | assert(prof_recent_alloc_max_get(tsd) != 0); | ||
| 325 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 326 | reserve = prof_recent_allocate_node(tsd_tsdn(tsd)); | ||
| 327 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 328 | prof_recent_alloc_assert_count(tsd); | ||
| 329 | } | ||
| 330 | |||
| 331 | if (prof_recent_alloc_max_get(tsd) == 0) { | ||
| 332 | assert(prof_recent_alloc_is_empty(tsd)); | ||
| 333 | goto label_rollback; | ||
| 334 | } | ||
| 335 | |||
| 336 | prof_tctx_t *old_alloc_tctx, *old_dalloc_tctx; | ||
| 337 | if (prof_recent_alloc_count == prof_recent_alloc_max_get(tsd)) { | ||
| 338 | /* If upper limit is reached, rotate the head. */ | ||
| 339 | assert(prof_recent_alloc_max_get(tsd) != -1); | ||
| 340 | assert(!prof_recent_alloc_is_empty(tsd)); | ||
| 341 | prof_recent_t *head = ql_first(&prof_recent_alloc_list); | ||
| 342 | old_alloc_tctx = head->alloc_tctx; | ||
| 343 | assert(old_alloc_tctx != NULL); | ||
| 344 | old_dalloc_tctx = head->dalloc_tctx; | ||
| 345 | prof_recent_alloc_evict_edata(tsd, head); | ||
| 346 | ql_rotate(&prof_recent_alloc_list, link); | ||
| 347 | } else { | ||
| 348 | /* Otherwise make use of the new node. */ | ||
| 349 | assert(prof_recent_alloc_max_get(tsd) == -1 || | ||
| 350 | prof_recent_alloc_count < prof_recent_alloc_max_get(tsd)); | ||
| 351 | if (reserve == NULL) { | ||
| 352 | goto label_rollback; | ||
| 353 | } | ||
| 354 | ql_elm_new(reserve, link); | ||
| 355 | ql_tail_insert(&prof_recent_alloc_list, reserve, link); | ||
| 356 | reserve = NULL; | ||
| 357 | old_alloc_tctx = NULL; | ||
| 358 | old_dalloc_tctx = NULL; | ||
| 359 | ++prof_recent_alloc_count; | ||
| 360 | } | ||
| 361 | |||
| 362 | /* Fill content into the tail node. */ | ||
| 363 | prof_recent_t *tail = ql_last(&prof_recent_alloc_list, link); | ||
| 364 | assert(tail != NULL); | ||
| 365 | tail->size = size; | ||
| 366 | tail->usize = usize; | ||
| 367 | nstime_copy(&tail->alloc_time, edata_prof_alloc_time_get(edata)); | ||
| 368 | tail->alloc_tctx = tctx; | ||
| 369 | nstime_init_zero(&tail->dalloc_time); | ||
| 370 | tail->dalloc_tctx = NULL; | ||
| 371 | edata_prof_recent_alloc_set(tsd, edata, tail); | ||
| 372 | |||
| 373 | assert(!prof_recent_alloc_is_empty(tsd)); | ||
| 374 | prof_recent_alloc_assert_count(tsd); | ||
| 375 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 376 | |||
| 377 | if (reserve != NULL) { | ||
| 378 | prof_recent_free_node(tsd_tsdn(tsd), reserve); | ||
| 379 | } | ||
| 380 | |||
| 381 | /* | ||
| 382 | * Asynchronously handle the tctx of the old node, so that there's no | ||
| 383 | * simultaneous holdings of prof_recent_alloc_mtx and tdata->lock. | ||
| 384 | * In the worst case this may delay the tctx release but it's better | ||
| 385 | * than holding prof_recent_alloc_mtx for longer. | ||
| 386 | */ | ||
| 387 | if (old_alloc_tctx != NULL) { | ||
| 388 | decrement_recent_count(tsd, old_alloc_tctx); | ||
| 389 | } | ||
| 390 | if (old_dalloc_tctx != NULL) { | ||
| 391 | decrement_recent_count(tsd, old_dalloc_tctx); | ||
| 392 | } | ||
| 393 | return; | ||
| 394 | |||
| 395 | label_rollback: | ||
| 396 | assert(edata_prof_recent_alloc_get(tsd, edata) == NULL); | ||
| 397 | prof_recent_alloc_assert_count(tsd); | ||
| 398 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 399 | if (reserve != NULL) { | ||
| 400 | prof_recent_free_node(tsd_tsdn(tsd), reserve); | ||
| 401 | } | ||
| 402 | decrement_recent_count(tsd, tctx); | ||
| 403 | } | ||
| 404 | |||
| 405 | ssize_t | ||
| 406 | prof_recent_alloc_max_ctl_read() { | ||
| 407 | cassert(config_prof); | ||
| 408 | /* Don't bother to acquire the lock. */ | ||
| 409 | return prof_recent_alloc_max_get_no_lock(); | ||
| 410 | } | ||
| 411 | |||
| 412 | static void | ||
| 413 | prof_recent_alloc_restore_locked(tsd_t *tsd, prof_recent_list_t *to_delete) { | ||
| 414 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 415 | ssize_t max = prof_recent_alloc_max_get(tsd); | ||
| 416 | if (max == -1 || prof_recent_alloc_count <= max) { | ||
| 417 | /* Easy case - no need to alter the list. */ | ||
| 418 | ql_new(to_delete); | ||
| 419 | prof_recent_alloc_assert_count(tsd); | ||
| 420 | return; | ||
| 421 | } | ||
| 422 | |||
| 423 | prof_recent_t *node; | ||
| 424 | ql_foreach(node, &prof_recent_alloc_list, link) { | ||
| 425 | if (prof_recent_alloc_count == max) { | ||
| 426 | break; | ||
| 427 | } | ||
| 428 | prof_recent_alloc_evict_edata(tsd, node); | ||
| 429 | --prof_recent_alloc_count; | ||
| 430 | } | ||
| 431 | assert(prof_recent_alloc_count == max); | ||
| 432 | |||
| 433 | ql_move(to_delete, &prof_recent_alloc_list); | ||
| 434 | if (max == 0) { | ||
| 435 | assert(node == NULL); | ||
| 436 | } else { | ||
| 437 | assert(node != NULL); | ||
| 438 | ql_split(to_delete, node, &prof_recent_alloc_list, link); | ||
| 439 | } | ||
| 440 | assert(!ql_empty(to_delete)); | ||
| 441 | prof_recent_alloc_assert_count(tsd); | ||
| 442 | } | ||
| 443 | |||
| 444 | static void | ||
| 445 | prof_recent_alloc_async_cleanup(tsd_t *tsd, prof_recent_list_t *to_delete) { | ||
| 446 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_dump_mtx); | ||
| 447 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 448 | while (!ql_empty(to_delete)) { | ||
| 449 | prof_recent_t *node = ql_first(to_delete); | ||
| 450 | ql_remove(to_delete, node, link); | ||
| 451 | decrement_recent_count(tsd, node->alloc_tctx); | ||
| 452 | if (node->dalloc_tctx != NULL) { | ||
| 453 | decrement_recent_count(tsd, node->dalloc_tctx); | ||
| 454 | } | ||
| 455 | prof_recent_free_node(tsd_tsdn(tsd), node); | ||
| 456 | } | ||
| 457 | } | ||
| 458 | |||
| 459 | ssize_t | ||
| 460 | prof_recent_alloc_max_ctl_write(tsd_t *tsd, ssize_t max) { | ||
| 461 | cassert(config_prof); | ||
| 462 | assert(max >= -1); | ||
| 463 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 464 | prof_recent_alloc_assert_count(tsd); | ||
| 465 | const ssize_t old_max = prof_recent_alloc_max_update(tsd, max); | ||
| 466 | prof_recent_list_t to_delete; | ||
| 467 | prof_recent_alloc_restore_locked(tsd, &to_delete); | ||
| 468 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 469 | prof_recent_alloc_async_cleanup(tsd, &to_delete); | ||
| 470 | return old_max; | ||
| 471 | } | ||
| 472 | |||
| 473 | static void | ||
| 474 | prof_recent_alloc_dump_bt(emitter_t *emitter, prof_tctx_t *tctx) { | ||
| 475 | char bt_buf[2 * sizeof(intptr_t) + 3]; | ||
| 476 | char *s = bt_buf; | ||
| 477 | assert(tctx != NULL); | ||
| 478 | prof_bt_t *bt = &tctx->gctx->bt; | ||
| 479 | for (size_t i = 0; i < bt->len; ++i) { | ||
| 480 | malloc_snprintf(bt_buf, sizeof(bt_buf), "%p", bt->vec[i]); | ||
| 481 | emitter_json_value(emitter, emitter_type_string, &s); | ||
| 482 | } | ||
| 483 | } | ||
| 484 | |||
| 485 | static void | ||
| 486 | prof_recent_alloc_dump_node(emitter_t *emitter, prof_recent_t *node) { | ||
| 487 | emitter_json_object_begin(emitter); | ||
| 488 | |||
| 489 | emitter_json_kv(emitter, "size", emitter_type_size, &node->size); | ||
| 490 | emitter_json_kv(emitter, "usize", emitter_type_size, &node->usize); | ||
| 491 | bool released = prof_recent_alloc_edata_get_no_lock(node) == NULL; | ||
| 492 | emitter_json_kv(emitter, "released", emitter_type_bool, &released); | ||
| 493 | |||
| 494 | emitter_json_kv(emitter, "alloc_thread_uid", emitter_type_uint64, | ||
| 495 | &node->alloc_tctx->thr_uid); | ||
| 496 | prof_tdata_t *alloc_tdata = node->alloc_tctx->tdata; | ||
| 497 | assert(alloc_tdata != NULL); | ||
| 498 | if (alloc_tdata->thread_name != NULL) { | ||
| 499 | emitter_json_kv(emitter, "alloc_thread_name", | ||
| 500 | emitter_type_string, &alloc_tdata->thread_name); | ||
| 501 | } | ||
| 502 | uint64_t alloc_time_ns = nstime_ns(&node->alloc_time); | ||
| 503 | emitter_json_kv(emitter, "alloc_time", emitter_type_uint64, | ||
| 504 | &alloc_time_ns); | ||
| 505 | emitter_json_array_kv_begin(emitter, "alloc_trace"); | ||
| 506 | prof_recent_alloc_dump_bt(emitter, node->alloc_tctx); | ||
| 507 | emitter_json_array_end(emitter); | ||
| 508 | |||
| 509 | if (released && node->dalloc_tctx != NULL) { | ||
| 510 | emitter_json_kv(emitter, "dalloc_thread_uid", | ||
| 511 | emitter_type_uint64, &node->dalloc_tctx->thr_uid); | ||
| 512 | prof_tdata_t *dalloc_tdata = node->dalloc_tctx->tdata; | ||
| 513 | assert(dalloc_tdata != NULL); | ||
| 514 | if (dalloc_tdata->thread_name != NULL) { | ||
| 515 | emitter_json_kv(emitter, "dalloc_thread_name", | ||
| 516 | emitter_type_string, &dalloc_tdata->thread_name); | ||
| 517 | } | ||
| 518 | assert(!nstime_equals_zero(&node->dalloc_time)); | ||
| 519 | uint64_t dalloc_time_ns = nstime_ns(&node->dalloc_time); | ||
| 520 | emitter_json_kv(emitter, "dalloc_time", emitter_type_uint64, | ||
| 521 | &dalloc_time_ns); | ||
| 522 | emitter_json_array_kv_begin(emitter, "dalloc_trace"); | ||
| 523 | prof_recent_alloc_dump_bt(emitter, node->dalloc_tctx); | ||
| 524 | emitter_json_array_end(emitter); | ||
| 525 | } | ||
| 526 | |||
| 527 | emitter_json_object_end(emitter); | ||
| 528 | } | ||
| 529 | |||
| 530 | #define PROF_RECENT_PRINT_BUFSIZE 65536 | ||
| 531 | JEMALLOC_COLD | ||
| 532 | void | ||
| 533 | prof_recent_alloc_dump(tsd_t *tsd, write_cb_t *write_cb, void *cbopaque) { | ||
| 534 | cassert(config_prof); | ||
| 535 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_dump_mtx); | ||
| 536 | buf_writer_t buf_writer; | ||
| 537 | buf_writer_init(tsd_tsdn(tsd), &buf_writer, write_cb, cbopaque, NULL, | ||
| 538 | PROF_RECENT_PRINT_BUFSIZE); | ||
| 539 | emitter_t emitter; | ||
| 540 | emitter_init(&emitter, emitter_output_json_compact, buf_writer_cb, | ||
| 541 | &buf_writer); | ||
| 542 | prof_recent_list_t temp_list; | ||
| 543 | |||
| 544 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 545 | prof_recent_alloc_assert_count(tsd); | ||
| 546 | ssize_t dump_max = prof_recent_alloc_max_get(tsd); | ||
| 547 | ql_move(&temp_list, &prof_recent_alloc_list); | ||
| 548 | ssize_t dump_count = prof_recent_alloc_count; | ||
| 549 | prof_recent_alloc_count = 0; | ||
| 550 | prof_recent_alloc_assert_count(tsd); | ||
| 551 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 552 | |||
| 553 | emitter_begin(&emitter); | ||
| 554 | uint64_t sample_interval = (uint64_t)1U << lg_prof_sample; | ||
| 555 | emitter_json_kv(&emitter, "sample_interval", emitter_type_uint64, | ||
| 556 | &sample_interval); | ||
| 557 | emitter_json_kv(&emitter, "recent_alloc_max", emitter_type_ssize, | ||
| 558 | &dump_max); | ||
| 559 | emitter_json_array_kv_begin(&emitter, "recent_alloc"); | ||
| 560 | prof_recent_t *node; | ||
| 561 | ql_foreach(node, &temp_list, link) { | ||
| 562 | prof_recent_alloc_dump_node(&emitter, node); | ||
| 563 | } | ||
| 564 | emitter_json_array_end(&emitter); | ||
| 565 | emitter_end(&emitter); | ||
| 566 | |||
| 567 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 568 | prof_recent_alloc_assert_count(tsd); | ||
| 569 | ql_concat(&temp_list, &prof_recent_alloc_list, link); | ||
| 570 | ql_move(&prof_recent_alloc_list, &temp_list); | ||
| 571 | prof_recent_alloc_count += dump_count; | ||
| 572 | prof_recent_alloc_restore_locked(tsd, &temp_list); | ||
| 573 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); | ||
| 574 | |||
| 575 | buf_writer_terminate(tsd_tsdn(tsd), &buf_writer); | ||
| 576 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_dump_mtx); | ||
| 577 | |||
| 578 | prof_recent_alloc_async_cleanup(tsd, &temp_list); | ||
| 579 | } | ||
| 580 | #undef PROF_RECENT_PRINT_BUFSIZE | ||
| 581 | |||
| 582 | bool | ||
| 583 | prof_recent_init() { | ||
| 584 | cassert(config_prof); | ||
| 585 | prof_recent_alloc_max_init(); | ||
| 586 | |||
| 587 | if (malloc_mutex_init(&prof_recent_alloc_mtx, "prof_recent_alloc", | ||
| 588 | WITNESS_RANK_PROF_RECENT_ALLOC, malloc_mutex_rank_exclusive)) { | ||
| 589 | return true; | ||
| 590 | } | ||
| 591 | |||
| 592 | if (malloc_mutex_init(&prof_recent_dump_mtx, "prof_recent_dump", | ||
| 593 | WITNESS_RANK_PROF_RECENT_DUMP, malloc_mutex_rank_exclusive)) { | ||
| 594 | return true; | ||
| 595 | } | ||
| 596 | |||
| 597 | ql_new(&prof_recent_alloc_list); | ||
| 598 | |||
| 599 | return false; | ||
| 600 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/prof_stats.c b/examples/redis-unstable/deps/jemalloc/src/prof_stats.c deleted file mode 100644 index 5d1a506..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/prof_stats.c +++ /dev/null | |||
| @@ -1,57 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/prof_stats.h" | ||
| 5 | |||
| 6 | bool opt_prof_stats = false; | ||
| 7 | malloc_mutex_t prof_stats_mtx; | ||
| 8 | static prof_stats_t prof_stats_live[PROF_SC_NSIZES]; | ||
| 9 | static prof_stats_t prof_stats_accum[PROF_SC_NSIZES]; | ||
| 10 | |||
| 11 | static void | ||
| 12 | prof_stats_enter(tsd_t *tsd, szind_t ind) { | ||
| 13 | assert(opt_prof && opt_prof_stats); | ||
| 14 | assert(ind < SC_NSIZES); | ||
| 15 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_stats_mtx); | ||
| 16 | } | ||
| 17 | |||
| 18 | static void | ||
| 19 | prof_stats_leave(tsd_t *tsd) { | ||
| 20 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_stats_mtx); | ||
| 21 | } | ||
| 22 | |||
| 23 | void | ||
| 24 | prof_stats_inc(tsd_t *tsd, szind_t ind, size_t size) { | ||
| 25 | cassert(config_prof); | ||
| 26 | prof_stats_enter(tsd, ind); | ||
| 27 | prof_stats_live[ind].req_sum += size; | ||
| 28 | prof_stats_live[ind].count++; | ||
| 29 | prof_stats_accum[ind].req_sum += size; | ||
| 30 | prof_stats_accum[ind].count++; | ||
| 31 | prof_stats_leave(tsd); | ||
| 32 | } | ||
| 33 | |||
| 34 | void | ||
| 35 | prof_stats_dec(tsd_t *tsd, szind_t ind, size_t size) { | ||
| 36 | cassert(config_prof); | ||
| 37 | prof_stats_enter(tsd, ind); | ||
| 38 | prof_stats_live[ind].req_sum -= size; | ||
| 39 | prof_stats_live[ind].count--; | ||
| 40 | prof_stats_leave(tsd); | ||
| 41 | } | ||
| 42 | |||
| 43 | void | ||
| 44 | prof_stats_get_live(tsd_t *tsd, szind_t ind, prof_stats_t *stats) { | ||
| 45 | cassert(config_prof); | ||
| 46 | prof_stats_enter(tsd, ind); | ||
| 47 | memcpy(stats, &prof_stats_live[ind], sizeof(prof_stats_t)); | ||
| 48 | prof_stats_leave(tsd); | ||
| 49 | } | ||
| 50 | |||
| 51 | void | ||
| 52 | prof_stats_get_accum(tsd_t *tsd, szind_t ind, prof_stats_t *stats) { | ||
| 53 | cassert(config_prof); | ||
| 54 | prof_stats_enter(tsd, ind); | ||
| 55 | memcpy(stats, &prof_stats_accum[ind], sizeof(prof_stats_t)); | ||
| 56 | prof_stats_leave(tsd); | ||
| 57 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/prof_sys.c b/examples/redis-unstable/deps/jemalloc/src/prof_sys.c deleted file mode 100644 index b5f1f5b..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/prof_sys.c +++ /dev/null | |||
| @@ -1,669 +0,0 @@ | |||
| 1 | #define JEMALLOC_PROF_SYS_C_ | ||
| 2 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 3 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 4 | |||
| 5 | #include "jemalloc/internal/buf_writer.h" | ||
| 6 | #include "jemalloc/internal/ctl.h" | ||
| 7 | #include "jemalloc/internal/prof_data.h" | ||
| 8 | #include "jemalloc/internal/prof_sys.h" | ||
| 9 | |||
| 10 | #ifdef JEMALLOC_PROF_LIBUNWIND | ||
| 11 | #define UNW_LOCAL_ONLY | ||
| 12 | #include <libunwind.h> | ||
| 13 | #endif | ||
| 14 | |||
| 15 | #ifdef JEMALLOC_PROF_LIBGCC | ||
| 16 | /* | ||
| 17 | * We have a circular dependency -- jemalloc_internal.h tells us if we should | ||
| 18 | * use libgcc's unwinding functionality, but after we've included that, we've | ||
| 19 | * already hooked _Unwind_Backtrace. We'll temporarily disable hooking. | ||
| 20 | */ | ||
| 21 | #undef _Unwind_Backtrace | ||
| 22 | #include <unwind.h> | ||
| 23 | #define _Unwind_Backtrace JEMALLOC_TEST_HOOK(_Unwind_Backtrace, test_hooks_libc_hook) | ||
| 24 | #endif | ||
| 25 | |||
| 26 | /******************************************************************************/ | ||
| 27 | |||
| 28 | malloc_mutex_t prof_dump_filename_mtx; | ||
| 29 | |||
| 30 | bool prof_do_mock = false; | ||
| 31 | |||
| 32 | static uint64_t prof_dump_seq; | ||
| 33 | static uint64_t prof_dump_iseq; | ||
| 34 | static uint64_t prof_dump_mseq; | ||
| 35 | static uint64_t prof_dump_useq; | ||
| 36 | |||
| 37 | static char *prof_prefix = NULL; | ||
| 38 | |||
| 39 | /* The fallback allocator profiling functionality will use. */ | ||
| 40 | base_t *prof_base; | ||
| 41 | |||
| 42 | void | ||
| 43 | bt_init(prof_bt_t *bt, void **vec) { | ||
| 44 | cassert(config_prof); | ||
| 45 | |||
| 46 | bt->vec = vec; | ||
| 47 | bt->len = 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | #ifdef JEMALLOC_PROF_LIBUNWIND | ||
| 51 | static void | ||
| 52 | prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { | ||
| 53 | int nframes; | ||
| 54 | |||
| 55 | cassert(config_prof); | ||
| 56 | assert(*len == 0); | ||
| 57 | assert(vec != NULL); | ||
| 58 | assert(max_len == PROF_BT_MAX); | ||
| 59 | |||
| 60 | nframes = unw_backtrace(vec, PROF_BT_MAX); | ||
| 61 | if (nframes <= 0) { | ||
| 62 | return; | ||
| 63 | } | ||
| 64 | *len = nframes; | ||
| 65 | } | ||
| 66 | #elif (defined(JEMALLOC_PROF_LIBGCC)) | ||
| 67 | static _Unwind_Reason_Code | ||
| 68 | prof_unwind_init_callback(struct _Unwind_Context *context, void *arg) { | ||
| 69 | cassert(config_prof); | ||
| 70 | |||
| 71 | return _URC_NO_REASON; | ||
| 72 | } | ||
| 73 | |||
| 74 | static _Unwind_Reason_Code | ||
| 75 | prof_unwind_callback(struct _Unwind_Context *context, void *arg) { | ||
| 76 | prof_unwind_data_t *data = (prof_unwind_data_t *)arg; | ||
| 77 | void *ip; | ||
| 78 | |||
| 79 | cassert(config_prof); | ||
| 80 | |||
| 81 | ip = (void *)_Unwind_GetIP(context); | ||
| 82 | if (ip == NULL) { | ||
| 83 | return _URC_END_OF_STACK; | ||
| 84 | } | ||
| 85 | data->vec[*data->len] = ip; | ||
| 86 | (*data->len)++; | ||
| 87 | if (*data->len == data->max) { | ||
| 88 | return _URC_END_OF_STACK; | ||
| 89 | } | ||
| 90 | |||
| 91 | return _URC_NO_REASON; | ||
| 92 | } | ||
| 93 | |||
| 94 | static void | ||
| 95 | prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { | ||
| 96 | prof_unwind_data_t data = {vec, len, max_len}; | ||
| 97 | |||
| 98 | cassert(config_prof); | ||
| 99 | assert(vec != NULL); | ||
| 100 | assert(max_len == PROF_BT_MAX); | ||
| 101 | |||
| 102 | _Unwind_Backtrace(prof_unwind_callback, &data); | ||
| 103 | } | ||
| 104 | #elif (defined(JEMALLOC_PROF_GCC)) | ||
| 105 | static void | ||
| 106 | prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { | ||
| 107 | #define BT_FRAME(i) \ | ||
| 108 | if ((i) < max_len) { \ | ||
| 109 | void *p; \ | ||
| 110 | if (__builtin_frame_address(i) == 0) { \ | ||
| 111 | return; \ | ||
| 112 | } \ | ||
| 113 | p = __builtin_return_address(i); \ | ||
| 114 | if (p == NULL) { \ | ||
| 115 | return; \ | ||
| 116 | } \ | ||
| 117 | vec[(i)] = p; \ | ||
| 118 | *len = (i) + 1; \ | ||
| 119 | } else { \ | ||
| 120 | return; \ | ||
| 121 | } | ||
| 122 | |||
| 123 | cassert(config_prof); | ||
| 124 | assert(vec != NULL); | ||
| 125 | assert(max_len == PROF_BT_MAX); | ||
| 126 | |||
| 127 | BT_FRAME(0) | ||
| 128 | BT_FRAME(1) | ||
| 129 | BT_FRAME(2) | ||
| 130 | BT_FRAME(3) | ||
| 131 | BT_FRAME(4) | ||
| 132 | BT_FRAME(5) | ||
| 133 | BT_FRAME(6) | ||
| 134 | BT_FRAME(7) | ||
| 135 | BT_FRAME(8) | ||
| 136 | BT_FRAME(9) | ||
| 137 | |||
| 138 | BT_FRAME(10) | ||
| 139 | BT_FRAME(11) | ||
| 140 | BT_FRAME(12) | ||
| 141 | BT_FRAME(13) | ||
| 142 | BT_FRAME(14) | ||
| 143 | BT_FRAME(15) | ||
| 144 | BT_FRAME(16) | ||
| 145 | BT_FRAME(17) | ||
| 146 | BT_FRAME(18) | ||
| 147 | BT_FRAME(19) | ||
| 148 | |||
| 149 | BT_FRAME(20) | ||
| 150 | BT_FRAME(21) | ||
| 151 | BT_FRAME(22) | ||
| 152 | BT_FRAME(23) | ||
| 153 | BT_FRAME(24) | ||
| 154 | BT_FRAME(25) | ||
| 155 | BT_FRAME(26) | ||
| 156 | BT_FRAME(27) | ||
| 157 | BT_FRAME(28) | ||
| 158 | BT_FRAME(29) | ||
| 159 | |||
| 160 | BT_FRAME(30) | ||
| 161 | BT_FRAME(31) | ||
| 162 | BT_FRAME(32) | ||
| 163 | BT_FRAME(33) | ||
| 164 | BT_FRAME(34) | ||
| 165 | BT_FRAME(35) | ||
| 166 | BT_FRAME(36) | ||
| 167 | BT_FRAME(37) | ||
| 168 | BT_FRAME(38) | ||
| 169 | BT_FRAME(39) | ||
| 170 | |||
| 171 | BT_FRAME(40) | ||
| 172 | BT_FRAME(41) | ||
| 173 | BT_FRAME(42) | ||
| 174 | BT_FRAME(43) | ||
| 175 | BT_FRAME(44) | ||
| 176 | BT_FRAME(45) | ||
| 177 | BT_FRAME(46) | ||
| 178 | BT_FRAME(47) | ||
| 179 | BT_FRAME(48) | ||
| 180 | BT_FRAME(49) | ||
| 181 | |||
| 182 | BT_FRAME(50) | ||
| 183 | BT_FRAME(51) | ||
| 184 | BT_FRAME(52) | ||
| 185 | BT_FRAME(53) | ||
| 186 | BT_FRAME(54) | ||
| 187 | BT_FRAME(55) | ||
| 188 | BT_FRAME(56) | ||
| 189 | BT_FRAME(57) | ||
| 190 | BT_FRAME(58) | ||
| 191 | BT_FRAME(59) | ||
| 192 | |||
| 193 | BT_FRAME(60) | ||
| 194 | BT_FRAME(61) | ||
| 195 | BT_FRAME(62) | ||
| 196 | BT_FRAME(63) | ||
| 197 | BT_FRAME(64) | ||
| 198 | BT_FRAME(65) | ||
| 199 | BT_FRAME(66) | ||
| 200 | BT_FRAME(67) | ||
| 201 | BT_FRAME(68) | ||
| 202 | BT_FRAME(69) | ||
| 203 | |||
| 204 | BT_FRAME(70) | ||
| 205 | BT_FRAME(71) | ||
| 206 | BT_FRAME(72) | ||
| 207 | BT_FRAME(73) | ||
| 208 | BT_FRAME(74) | ||
| 209 | BT_FRAME(75) | ||
| 210 | BT_FRAME(76) | ||
| 211 | BT_FRAME(77) | ||
| 212 | BT_FRAME(78) | ||
| 213 | BT_FRAME(79) | ||
| 214 | |||
| 215 | BT_FRAME(80) | ||
| 216 | BT_FRAME(81) | ||
| 217 | BT_FRAME(82) | ||
| 218 | BT_FRAME(83) | ||
| 219 | BT_FRAME(84) | ||
| 220 | BT_FRAME(85) | ||
| 221 | BT_FRAME(86) | ||
| 222 | BT_FRAME(87) | ||
| 223 | BT_FRAME(88) | ||
| 224 | BT_FRAME(89) | ||
| 225 | |||
| 226 | BT_FRAME(90) | ||
| 227 | BT_FRAME(91) | ||
| 228 | BT_FRAME(92) | ||
| 229 | BT_FRAME(93) | ||
| 230 | BT_FRAME(94) | ||
| 231 | BT_FRAME(95) | ||
| 232 | BT_FRAME(96) | ||
| 233 | BT_FRAME(97) | ||
| 234 | BT_FRAME(98) | ||
| 235 | BT_FRAME(99) | ||
| 236 | |||
| 237 | BT_FRAME(100) | ||
| 238 | BT_FRAME(101) | ||
| 239 | BT_FRAME(102) | ||
| 240 | BT_FRAME(103) | ||
| 241 | BT_FRAME(104) | ||
| 242 | BT_FRAME(105) | ||
| 243 | BT_FRAME(106) | ||
| 244 | BT_FRAME(107) | ||
| 245 | BT_FRAME(108) | ||
| 246 | BT_FRAME(109) | ||
| 247 | |||
| 248 | BT_FRAME(110) | ||
| 249 | BT_FRAME(111) | ||
| 250 | BT_FRAME(112) | ||
| 251 | BT_FRAME(113) | ||
| 252 | BT_FRAME(114) | ||
| 253 | BT_FRAME(115) | ||
| 254 | BT_FRAME(116) | ||
| 255 | BT_FRAME(117) | ||
| 256 | BT_FRAME(118) | ||
| 257 | BT_FRAME(119) | ||
| 258 | |||
| 259 | BT_FRAME(120) | ||
| 260 | BT_FRAME(121) | ||
| 261 | BT_FRAME(122) | ||
| 262 | BT_FRAME(123) | ||
| 263 | BT_FRAME(124) | ||
| 264 | BT_FRAME(125) | ||
| 265 | BT_FRAME(126) | ||
| 266 | BT_FRAME(127) | ||
| 267 | #undef BT_FRAME | ||
| 268 | } | ||
| 269 | #else | ||
| 270 | static void | ||
| 271 | prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) { | ||
| 272 | cassert(config_prof); | ||
| 273 | not_reached(); | ||
| 274 | } | ||
| 275 | #endif | ||
| 276 | |||
| 277 | void | ||
| 278 | prof_backtrace(tsd_t *tsd, prof_bt_t *bt) { | ||
| 279 | cassert(config_prof); | ||
| 280 | prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get(); | ||
| 281 | assert(prof_backtrace_hook != NULL); | ||
| 282 | |||
| 283 | pre_reentrancy(tsd, NULL); | ||
| 284 | prof_backtrace_hook(bt->vec, &bt->len, PROF_BT_MAX); | ||
| 285 | post_reentrancy(tsd); | ||
| 286 | } | ||
| 287 | |||
| 288 | void | ||
| 289 | prof_hooks_init() { | ||
| 290 | prof_backtrace_hook_set(&prof_backtrace_impl); | ||
| 291 | prof_dump_hook_set(NULL); | ||
| 292 | } | ||
| 293 | |||
| 294 | void | ||
| 295 | prof_unwind_init() { | ||
| 296 | #ifdef JEMALLOC_PROF_LIBGCC | ||
| 297 | /* | ||
| 298 | * Cause the backtracing machinery to allocate its internal | ||
| 299 | * state before enabling profiling. | ||
| 300 | */ | ||
| 301 | _Unwind_Backtrace(prof_unwind_init_callback, NULL); | ||
| 302 | #endif | ||
| 303 | } | ||
| 304 | |||
| 305 | static int | ||
| 306 | prof_sys_thread_name_read_impl(char *buf, size_t limit) { | ||
| 307 | #if defined(JEMALLOC_HAVE_PTHREAD_GETNAME_NP) | ||
| 308 | return pthread_getname_np(pthread_self(), buf, limit); | ||
| 309 | #elif defined(JEMALLOC_HAVE_PTHREAD_GET_NAME_NP) | ||
| 310 | pthread_get_name_np(pthread_self(), buf, limit); | ||
| 311 | return 0; | ||
| 312 | #else | ||
| 313 | return ENOSYS; | ||
| 314 | #endif | ||
| 315 | } | ||
| 316 | prof_sys_thread_name_read_t *JET_MUTABLE prof_sys_thread_name_read = | ||
| 317 | prof_sys_thread_name_read_impl; | ||
| 318 | |||
| 319 | void | ||
| 320 | prof_sys_thread_name_fetch(tsd_t *tsd) { | ||
| 321 | #define THREAD_NAME_MAX_LEN 16 | ||
| 322 | char buf[THREAD_NAME_MAX_LEN]; | ||
| 323 | if (!prof_sys_thread_name_read(buf, THREAD_NAME_MAX_LEN)) { | ||
| 324 | prof_thread_name_set_impl(tsd, buf); | ||
| 325 | } | ||
| 326 | #undef THREAD_NAME_MAX_LEN | ||
| 327 | } | ||
| 328 | |||
| 329 | int | ||
| 330 | prof_getpid(void) { | ||
| 331 | #ifdef _WIN32 | ||
| 332 | return GetCurrentProcessId(); | ||
| 333 | #else | ||
| 334 | return getpid(); | ||
| 335 | #endif | ||
| 336 | } | ||
| 337 | |||
| 338 | /* | ||
| 339 | * This buffer is rather large for stack allocation, so use a single buffer for | ||
| 340 | * all profile dumps; protected by prof_dump_mtx. | ||
| 341 | */ | ||
| 342 | static char prof_dump_buf[PROF_DUMP_BUFSIZE]; | ||
| 343 | |||
| 344 | typedef struct prof_dump_arg_s prof_dump_arg_t; | ||
| 345 | struct prof_dump_arg_s { | ||
| 346 | /* | ||
| 347 | * Whether error should be handled locally: if true, then we print out | ||
| 348 | * error message as well as abort (if opt_abort is true) when an error | ||
| 349 | * occurred, and we also report the error back to the caller in the end; | ||
| 350 | * if false, then we only report the error back to the caller in the | ||
| 351 | * end. | ||
| 352 | */ | ||
| 353 | const bool handle_error_locally; | ||
| 354 | /* | ||
| 355 | * Whether there has been an error in the dumping process, which could | ||
| 356 | * have happened either in file opening or in file writing. When an | ||
| 357 | * error has already occurred, we will stop further writing to the file. | ||
| 358 | */ | ||
| 359 | bool error; | ||
| 360 | /* File descriptor of the dump file. */ | ||
| 361 | int prof_dump_fd; | ||
| 362 | }; | ||
| 363 | |||
| 364 | static void | ||
| 365 | prof_dump_check_possible_error(prof_dump_arg_t *arg, bool err_cond, | ||
| 366 | const char *format, ...) { | ||
| 367 | assert(!arg->error); | ||
| 368 | if (!err_cond) { | ||
| 369 | return; | ||
| 370 | } | ||
| 371 | |||
| 372 | arg->error = true; | ||
| 373 | if (!arg->handle_error_locally) { | ||
| 374 | return; | ||
| 375 | } | ||
| 376 | |||
| 377 | va_list ap; | ||
| 378 | char buf[PROF_PRINTF_BUFSIZE]; | ||
| 379 | va_start(ap, format); | ||
| 380 | malloc_vsnprintf(buf, sizeof(buf), format, ap); | ||
| 381 | va_end(ap); | ||
| 382 | malloc_write(buf); | ||
| 383 | |||
| 384 | if (opt_abort) { | ||
| 385 | abort(); | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | static int | ||
| 390 | prof_dump_open_file_impl(const char *filename, int mode) { | ||
| 391 | return creat(filename, mode); | ||
| 392 | } | ||
| 393 | prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file = | ||
| 394 | prof_dump_open_file_impl; | ||
| 395 | |||
| 396 | static void | ||
| 397 | prof_dump_open(prof_dump_arg_t *arg, const char *filename) { | ||
| 398 | arg->prof_dump_fd = prof_dump_open_file(filename, 0644); | ||
| 399 | prof_dump_check_possible_error(arg, arg->prof_dump_fd == -1, | ||
| 400 | "<jemalloc>: failed to open \"%s\"\n", filename); | ||
| 401 | } | ||
| 402 | |||
| 403 | prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd; | ||
| 404 | |||
| 405 | static void | ||
| 406 | prof_dump_flush(void *opaque, const char *s) { | ||
| 407 | cassert(config_prof); | ||
| 408 | prof_dump_arg_t *arg = (prof_dump_arg_t *)opaque; | ||
| 409 | if (!arg->error) { | ||
| 410 | ssize_t err = prof_dump_write_file(arg->prof_dump_fd, s, | ||
| 411 | strlen(s)); | ||
| 412 | prof_dump_check_possible_error(arg, err == -1, | ||
| 413 | "<jemalloc>: failed to write during heap profile flush\n"); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | |||
| 417 | static void | ||
| 418 | prof_dump_close(prof_dump_arg_t *arg) { | ||
| 419 | if (arg->prof_dump_fd != -1) { | ||
| 420 | close(arg->prof_dump_fd); | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | #ifndef _WIN32 | ||
| 425 | JEMALLOC_FORMAT_PRINTF(1, 2) | ||
| 426 | static int | ||
| 427 | prof_open_maps_internal(const char *format, ...) { | ||
| 428 | int mfd; | ||
| 429 | va_list ap; | ||
| 430 | char filename[PATH_MAX + 1]; | ||
| 431 | |||
| 432 | va_start(ap, format); | ||
| 433 | malloc_vsnprintf(filename, sizeof(filename), format, ap); | ||
| 434 | va_end(ap); | ||
| 435 | |||
| 436 | #if defined(O_CLOEXEC) | ||
| 437 | mfd = open(filename, O_RDONLY | O_CLOEXEC); | ||
| 438 | #else | ||
| 439 | mfd = open(filename, O_RDONLY); | ||
| 440 | if (mfd != -1) { | ||
| 441 | fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC); | ||
| 442 | } | ||
| 443 | #endif | ||
| 444 | |||
| 445 | return mfd; | ||
| 446 | } | ||
| 447 | #endif | ||
| 448 | |||
| 449 | static int | ||
| 450 | prof_dump_open_maps_impl() { | ||
| 451 | int mfd; | ||
| 452 | |||
| 453 | cassert(config_prof); | ||
| 454 | #if defined(__FreeBSD__) || defined(__DragonFly__) | ||
| 455 | mfd = prof_open_maps_internal("/proc/curproc/map"); | ||
| 456 | #elif defined(_WIN32) | ||
| 457 | mfd = -1; // Not implemented | ||
| 458 | #else | ||
| 459 | int pid = prof_getpid(); | ||
| 460 | |||
| 461 | mfd = prof_open_maps_internal("/proc/%d/task/%d/maps", pid, pid); | ||
| 462 | if (mfd == -1) { | ||
| 463 | mfd = prof_open_maps_internal("/proc/%d/maps", pid); | ||
| 464 | } | ||
| 465 | #endif | ||
| 466 | return mfd; | ||
| 467 | } | ||
| 468 | prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps = | ||
| 469 | prof_dump_open_maps_impl; | ||
| 470 | |||
| 471 | static ssize_t | ||
| 472 | prof_dump_read_maps_cb(void *read_cbopaque, void *buf, size_t limit) { | ||
| 473 | int mfd = *(int *)read_cbopaque; | ||
| 474 | assert(mfd != -1); | ||
| 475 | return malloc_read_fd(mfd, buf, limit); | ||
| 476 | } | ||
| 477 | |||
| 478 | static void | ||
| 479 | prof_dump_maps(buf_writer_t *buf_writer) { | ||
| 480 | int mfd = prof_dump_open_maps(); | ||
| 481 | if (mfd == -1) { | ||
| 482 | return; | ||
| 483 | } | ||
| 484 | |||
| 485 | buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n"); | ||
| 486 | buf_writer_pipe(buf_writer, prof_dump_read_maps_cb, &mfd); | ||
| 487 | close(mfd); | ||
| 488 | } | ||
| 489 | |||
| 490 | static bool | ||
| 491 | prof_dump(tsd_t *tsd, bool propagate_err, const char *filename, | ||
| 492 | bool leakcheck) { | ||
| 493 | cassert(config_prof); | ||
| 494 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 495 | |||
| 496 | prof_tdata_t * tdata = prof_tdata_get(tsd, true); | ||
| 497 | if (tdata == NULL) { | ||
| 498 | return true; | ||
| 499 | } | ||
| 500 | |||
| 501 | prof_dump_arg_t arg = {/* handle_error_locally */ !propagate_err, | ||
| 502 | /* error */ false, /* prof_dump_fd */ -1}; | ||
| 503 | |||
| 504 | pre_reentrancy(tsd, NULL); | ||
| 505 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx); | ||
| 506 | |||
| 507 | prof_dump_open(&arg, filename); | ||
| 508 | buf_writer_t buf_writer; | ||
| 509 | bool err = buf_writer_init(tsd_tsdn(tsd), &buf_writer, prof_dump_flush, | ||
| 510 | &arg, prof_dump_buf, PROF_DUMP_BUFSIZE); | ||
| 511 | assert(!err); | ||
| 512 | prof_dump_impl(tsd, buf_writer_cb, &buf_writer, tdata, leakcheck); | ||
| 513 | prof_dump_maps(&buf_writer); | ||
| 514 | buf_writer_terminate(tsd_tsdn(tsd), &buf_writer); | ||
| 515 | prof_dump_close(&arg); | ||
| 516 | |||
| 517 | prof_dump_hook_t dump_hook = prof_dump_hook_get(); | ||
| 518 | if (dump_hook != NULL) { | ||
| 519 | dump_hook(filename); | ||
| 520 | } | ||
| 521 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx); | ||
| 522 | post_reentrancy(tsd); | ||
| 523 | |||
| 524 | return arg.error; | ||
| 525 | } | ||
| 526 | |||
| 527 | /* | ||
| 528 | * If profiling is off, then PROF_DUMP_FILENAME_LEN is 1, so we'll end up | ||
| 529 | * calling strncpy with a size of 0, which triggers a -Wstringop-truncation | ||
| 530 | * warning (strncpy can never actually be called in this case, since we bail out | ||
| 531 | * much earlier when config_prof is false). This function works around the | ||
| 532 | * warning to let us leave the warning on. | ||
| 533 | */ | ||
| 534 | static inline void | ||
| 535 | prof_strncpy(char *UNUSED dest, const char *UNUSED src, size_t UNUSED size) { | ||
| 536 | cassert(config_prof); | ||
| 537 | #ifdef JEMALLOC_PROF | ||
| 538 | strncpy(dest, src, size); | ||
| 539 | #endif | ||
| 540 | } | ||
| 541 | |||
| 542 | static const char * | ||
| 543 | prof_prefix_get(tsdn_t* tsdn) { | ||
| 544 | malloc_mutex_assert_owner(tsdn, &prof_dump_filename_mtx); | ||
| 545 | |||
| 546 | return prof_prefix == NULL ? opt_prof_prefix : prof_prefix; | ||
| 547 | } | ||
| 548 | |||
| 549 | static bool | ||
| 550 | prof_prefix_is_empty(tsdn_t *tsdn) { | ||
| 551 | malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); | ||
| 552 | bool ret = (prof_prefix_get(tsdn)[0] == '\0'); | ||
| 553 | malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); | ||
| 554 | return ret; | ||
| 555 | } | ||
| 556 | |||
| 557 | #define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1) | ||
| 558 | #define VSEQ_INVALID UINT64_C(0xffffffffffffffff) | ||
| 559 | static void | ||
| 560 | prof_dump_filename(tsd_t *tsd, char *filename, char v, uint64_t vseq) { | ||
| 561 | cassert(config_prof); | ||
| 562 | |||
| 563 | assert(tsd_reentrancy_level_get(tsd) == 0); | ||
| 564 | const char *prefix = prof_prefix_get(tsd_tsdn(tsd)); | ||
| 565 | |||
| 566 | if (vseq != VSEQ_INVALID) { | ||
| 567 | /* "<prefix>.<pid>.<seq>.v<vseq>.heap" */ | ||
| 568 | malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE, | ||
| 569 | "%s.%d.%"FMTu64".%c%"FMTu64".heap", prefix, prof_getpid(), | ||
| 570 | prof_dump_seq, v, vseq); | ||
| 571 | } else { | ||
| 572 | /* "<prefix>.<pid>.<seq>.<v>.heap" */ | ||
| 573 | malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE, | ||
| 574 | "%s.%d.%"FMTu64".%c.heap", prefix, prof_getpid(), | ||
| 575 | prof_dump_seq, v); | ||
| 576 | } | ||
| 577 | prof_dump_seq++; | ||
| 578 | } | ||
| 579 | |||
| 580 | void | ||
| 581 | prof_get_default_filename(tsdn_t *tsdn, char *filename, uint64_t ind) { | ||
| 582 | malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); | ||
| 583 | malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN, | ||
| 584 | "%s.%d.%"FMTu64".json", prof_prefix_get(tsdn), prof_getpid(), ind); | ||
| 585 | malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); | ||
| 586 | } | ||
| 587 | |||
| 588 | void | ||
| 589 | prof_fdump_impl(tsd_t *tsd) { | ||
| 590 | char filename[DUMP_FILENAME_BUFSIZE]; | ||
| 591 | |||
| 592 | assert(!prof_prefix_is_empty(tsd_tsdn(tsd))); | ||
| 593 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 594 | prof_dump_filename(tsd, filename, 'f', VSEQ_INVALID); | ||
| 595 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 596 | prof_dump(tsd, false, filename, opt_prof_leak); | ||
| 597 | } | ||
| 598 | |||
| 599 | bool | ||
| 600 | prof_prefix_set(tsdn_t *tsdn, const char *prefix) { | ||
| 601 | cassert(config_prof); | ||
| 602 | ctl_mtx_assert_held(tsdn); | ||
| 603 | malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); | ||
| 604 | if (prof_prefix == NULL) { | ||
| 605 | malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); | ||
| 606 | /* Everything is still guarded by ctl_mtx. */ | ||
| 607 | char *buffer = base_alloc(tsdn, prof_base, | ||
| 608 | PROF_DUMP_FILENAME_LEN, QUANTUM); | ||
| 609 | if (buffer == NULL) { | ||
| 610 | return true; | ||
| 611 | } | ||
| 612 | malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); | ||
| 613 | prof_prefix = buffer; | ||
| 614 | } | ||
| 615 | assert(prof_prefix != NULL); | ||
| 616 | |||
| 617 | prof_strncpy(prof_prefix, prefix, PROF_DUMP_FILENAME_LEN - 1); | ||
| 618 | prof_prefix[PROF_DUMP_FILENAME_LEN - 1] = '\0'; | ||
| 619 | malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); | ||
| 620 | |||
| 621 | return false; | ||
| 622 | } | ||
| 623 | |||
| 624 | void | ||
| 625 | prof_idump_impl(tsd_t *tsd) { | ||
| 626 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 627 | if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') { | ||
| 628 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 629 | return; | ||
| 630 | } | ||
| 631 | char filename[PATH_MAX + 1]; | ||
| 632 | prof_dump_filename(tsd, filename, 'i', prof_dump_iseq); | ||
| 633 | prof_dump_iseq++; | ||
| 634 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 635 | prof_dump(tsd, false, filename, false); | ||
| 636 | } | ||
| 637 | |||
| 638 | bool | ||
| 639 | prof_mdump_impl(tsd_t *tsd, const char *filename) { | ||
| 640 | char filename_buf[DUMP_FILENAME_BUFSIZE]; | ||
| 641 | if (filename == NULL) { | ||
| 642 | /* No filename specified, so automatically generate one. */ | ||
| 643 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 644 | if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') { | ||
| 645 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 646 | return true; | ||
| 647 | } | ||
| 648 | prof_dump_filename(tsd, filename_buf, 'm', prof_dump_mseq); | ||
| 649 | prof_dump_mseq++; | ||
| 650 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx); | ||
| 651 | filename = filename_buf; | ||
| 652 | } | ||
| 653 | return prof_dump(tsd, true, filename, false); | ||
| 654 | } | ||
| 655 | |||
| 656 | void | ||
| 657 | prof_gdump_impl(tsd_t *tsd) { | ||
| 658 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 659 | malloc_mutex_lock(tsdn, &prof_dump_filename_mtx); | ||
| 660 | if (prof_prefix_get(tsdn)[0] == '\0') { | ||
| 661 | malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); | ||
| 662 | return; | ||
| 663 | } | ||
| 664 | char filename[DUMP_FILENAME_BUFSIZE]; | ||
| 665 | prof_dump_filename(tsd, filename, 'u', prof_dump_useq); | ||
| 666 | prof_dump_useq++; | ||
| 667 | malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx); | ||
| 668 | prof_dump(tsd, false, filename, false); | ||
| 669 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/psset.c b/examples/redis-unstable/deps/jemalloc/src/psset.c deleted file mode 100644 index 9a8f054..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/psset.c +++ /dev/null | |||
| @@ -1,385 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/psset.h" | ||
| 5 | |||
| 6 | #include "jemalloc/internal/fb.h" | ||
| 7 | |||
| 8 | void | ||
| 9 | psset_init(psset_t *psset) { | ||
| 10 | for (unsigned i = 0; i < PSSET_NPSIZES; i++) { | ||
| 11 | hpdata_age_heap_new(&psset->pageslabs[i]); | ||
| 12 | } | ||
| 13 | fb_init(psset->pageslab_bitmap, PSSET_NPSIZES); | ||
| 14 | memset(&psset->merged_stats, 0, sizeof(psset->merged_stats)); | ||
| 15 | memset(&psset->stats, 0, sizeof(psset->stats)); | ||
| 16 | hpdata_empty_list_init(&psset->empty); | ||
| 17 | for (int i = 0; i < PSSET_NPURGE_LISTS; i++) { | ||
| 18 | hpdata_purge_list_init(&psset->to_purge[i]); | ||
| 19 | } | ||
| 20 | fb_init(psset->purge_bitmap, PSSET_NPURGE_LISTS); | ||
| 21 | hpdata_hugify_list_init(&psset->to_hugify); | ||
| 22 | } | ||
| 23 | |||
| 24 | static void | ||
| 25 | psset_bin_stats_accum(psset_bin_stats_t *dst, psset_bin_stats_t *src) { | ||
| 26 | dst->npageslabs += src->npageslabs; | ||
| 27 | dst->nactive += src->nactive; | ||
| 28 | dst->ndirty += src->ndirty; | ||
| 29 | } | ||
| 30 | |||
| 31 | void | ||
| 32 | psset_stats_accum(psset_stats_t *dst, psset_stats_t *src) { | ||
| 33 | psset_bin_stats_accum(&dst->full_slabs[0], &src->full_slabs[0]); | ||
| 34 | psset_bin_stats_accum(&dst->full_slabs[1], &src->full_slabs[1]); | ||
| 35 | psset_bin_stats_accum(&dst->empty_slabs[0], &src->empty_slabs[0]); | ||
| 36 | psset_bin_stats_accum(&dst->empty_slabs[1], &src->empty_slabs[1]); | ||
| 37 | for (pszind_t i = 0; i < PSSET_NPSIZES; i++) { | ||
| 38 | psset_bin_stats_accum(&dst->nonfull_slabs[i][0], | ||
| 39 | &src->nonfull_slabs[i][0]); | ||
| 40 | psset_bin_stats_accum(&dst->nonfull_slabs[i][1], | ||
| 41 | &src->nonfull_slabs[i][1]); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | /* | ||
| 46 | * The stats maintenance strategy is to remove a pageslab's contribution to the | ||
| 47 | * stats when we call psset_update_begin, and re-add it (to a potentially new | ||
| 48 | * bin) when we call psset_update_end. | ||
| 49 | */ | ||
| 50 | JEMALLOC_ALWAYS_INLINE void | ||
| 51 | psset_bin_stats_insert_remove(psset_t *psset, psset_bin_stats_t *binstats, | ||
| 52 | hpdata_t *ps, bool insert) { | ||
| 53 | size_t mul = insert ? (size_t)1 : (size_t)-1; | ||
| 54 | size_t huge_idx = (size_t)hpdata_huge_get(ps); | ||
| 55 | |||
| 56 | binstats[huge_idx].npageslabs += mul * 1; | ||
| 57 | binstats[huge_idx].nactive += mul * hpdata_nactive_get(ps); | ||
| 58 | binstats[huge_idx].ndirty += mul * hpdata_ndirty_get(ps); | ||
| 59 | |||
| 60 | psset->merged_stats.npageslabs += mul * 1; | ||
| 61 | psset->merged_stats.nactive += mul * hpdata_nactive_get(ps); | ||
| 62 | psset->merged_stats.ndirty += mul * hpdata_ndirty_get(ps); | ||
| 63 | |||
| 64 | if (config_debug) { | ||
| 65 | psset_bin_stats_t check_stats = {0}; | ||
| 66 | for (size_t huge = 0; huge <= 1; huge++) { | ||
| 67 | psset_bin_stats_accum(&check_stats, | ||
| 68 | &psset->stats.full_slabs[huge]); | ||
| 69 | psset_bin_stats_accum(&check_stats, | ||
| 70 | &psset->stats.empty_slabs[huge]); | ||
| 71 | for (pszind_t pind = 0; pind < PSSET_NPSIZES; pind++) { | ||
| 72 | psset_bin_stats_accum(&check_stats, | ||
| 73 | &psset->stats.nonfull_slabs[pind][huge]); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | assert(psset->merged_stats.npageslabs | ||
| 77 | == check_stats.npageslabs); | ||
| 78 | assert(psset->merged_stats.nactive == check_stats.nactive); | ||
| 79 | assert(psset->merged_stats.ndirty == check_stats.ndirty); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | static void | ||
| 84 | psset_bin_stats_insert(psset_t *psset, psset_bin_stats_t *binstats, | ||
| 85 | hpdata_t *ps) { | ||
| 86 | psset_bin_stats_insert_remove(psset, binstats, ps, true); | ||
| 87 | } | ||
| 88 | |||
| 89 | static void | ||
| 90 | psset_bin_stats_remove(psset_t *psset, psset_bin_stats_t *binstats, | ||
| 91 | hpdata_t *ps) { | ||
| 92 | psset_bin_stats_insert_remove(psset, binstats, ps, false); | ||
| 93 | } | ||
| 94 | |||
| 95 | static void | ||
| 96 | psset_hpdata_heap_remove(psset_t *psset, pszind_t pind, hpdata_t *ps) { | ||
| 97 | hpdata_age_heap_remove(&psset->pageslabs[pind], ps); | ||
| 98 | if (hpdata_age_heap_empty(&psset->pageslabs[pind])) { | ||
| 99 | fb_unset(psset->pageslab_bitmap, PSSET_NPSIZES, (size_t)pind); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | static void | ||
| 104 | psset_hpdata_heap_insert(psset_t *psset, pszind_t pind, hpdata_t *ps) { | ||
| 105 | if (hpdata_age_heap_empty(&psset->pageslabs[pind])) { | ||
| 106 | fb_set(psset->pageslab_bitmap, PSSET_NPSIZES, (size_t)pind); | ||
| 107 | } | ||
| 108 | hpdata_age_heap_insert(&psset->pageslabs[pind], ps); | ||
| 109 | } | ||
| 110 | |||
| 111 | static void | ||
| 112 | psset_stats_insert(psset_t* psset, hpdata_t *ps) { | ||
| 113 | if (hpdata_empty(ps)) { | ||
| 114 | psset_bin_stats_insert(psset, psset->stats.empty_slabs, ps); | ||
| 115 | } else if (hpdata_full(ps)) { | ||
| 116 | psset_bin_stats_insert(psset, psset->stats.full_slabs, ps); | ||
| 117 | } else { | ||
| 118 | size_t longest_free_range = hpdata_longest_free_range_get(ps); | ||
| 119 | |||
| 120 | pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( | ||
| 121 | longest_free_range << LG_PAGE)); | ||
| 122 | assert(pind < PSSET_NPSIZES); | ||
| 123 | |||
| 124 | psset_bin_stats_insert(psset, psset->stats.nonfull_slabs[pind], | ||
| 125 | ps); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | static void | ||
| 130 | psset_stats_remove(psset_t *psset, hpdata_t *ps) { | ||
| 131 | if (hpdata_empty(ps)) { | ||
| 132 | psset_bin_stats_remove(psset, psset->stats.empty_slabs, ps); | ||
| 133 | } else if (hpdata_full(ps)) { | ||
| 134 | psset_bin_stats_remove(psset, psset->stats.full_slabs, ps); | ||
| 135 | } else { | ||
| 136 | size_t longest_free_range = hpdata_longest_free_range_get(ps); | ||
| 137 | |||
| 138 | pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( | ||
| 139 | longest_free_range << LG_PAGE)); | ||
| 140 | assert(pind < PSSET_NPSIZES); | ||
| 141 | |||
| 142 | psset_bin_stats_remove(psset, psset->stats.nonfull_slabs[pind], | ||
| 143 | ps); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | /* | ||
| 148 | * Put ps into some container so that it can be found during future allocation | ||
| 149 | * requests. | ||
| 150 | */ | ||
| 151 | static void | ||
| 152 | psset_alloc_container_insert(psset_t *psset, hpdata_t *ps) { | ||
| 153 | assert(!hpdata_in_psset_alloc_container_get(ps)); | ||
| 154 | hpdata_in_psset_alloc_container_set(ps, true); | ||
| 155 | if (hpdata_empty(ps)) { | ||
| 156 | /* | ||
| 157 | * This prepend, paired with popping the head in psset_fit, | ||
| 158 | * means we implement LIFO ordering for the empty slabs set, | ||
| 159 | * which seems reasonable. | ||
| 160 | */ | ||
| 161 | hpdata_empty_list_prepend(&psset->empty, ps); | ||
| 162 | } else if (hpdata_full(ps)) { | ||
| 163 | /* | ||
| 164 | * We don't need to keep track of the full slabs; we're never | ||
| 165 | * going to return them from a psset_pick_alloc call. | ||
| 166 | */ | ||
| 167 | } else { | ||
| 168 | size_t longest_free_range = hpdata_longest_free_range_get(ps); | ||
| 169 | |||
| 170 | pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( | ||
| 171 | longest_free_range << LG_PAGE)); | ||
| 172 | assert(pind < PSSET_NPSIZES); | ||
| 173 | |||
| 174 | psset_hpdata_heap_insert(psset, pind, ps); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | /* Remove ps from those collections. */ | ||
| 179 | static void | ||
| 180 | psset_alloc_container_remove(psset_t *psset, hpdata_t *ps) { | ||
| 181 | assert(hpdata_in_psset_alloc_container_get(ps)); | ||
| 182 | hpdata_in_psset_alloc_container_set(ps, false); | ||
| 183 | |||
| 184 | if (hpdata_empty(ps)) { | ||
| 185 | hpdata_empty_list_remove(&psset->empty, ps); | ||
| 186 | } else if (hpdata_full(ps)) { | ||
| 187 | /* Same as above -- do nothing in this case. */ | ||
| 188 | } else { | ||
| 189 | size_t longest_free_range = hpdata_longest_free_range_get(ps); | ||
| 190 | |||
| 191 | pszind_t pind = sz_psz2ind(sz_psz_quantize_floor( | ||
| 192 | longest_free_range << LG_PAGE)); | ||
| 193 | assert(pind < PSSET_NPSIZES); | ||
| 194 | |||
| 195 | psset_hpdata_heap_remove(psset, pind, ps); | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | static size_t | ||
| 200 | psset_purge_list_ind(hpdata_t *ps) { | ||
| 201 | size_t ndirty = hpdata_ndirty_get(ps); | ||
| 202 | /* Shouldn't have something with no dirty pages purgeable. */ | ||
| 203 | assert(ndirty > 0); | ||
| 204 | /* | ||
| 205 | * Higher indices correspond to lists we'd like to purge earlier; make | ||
| 206 | * the two highest indices correspond to empty lists, which we attempt | ||
| 207 | * to purge before purging any non-empty list. This has two advantages: | ||
| 208 | * - Empty page slabs are the least likely to get reused (we'll only | ||
| 209 | * pick them for an allocation if we have no other choice). | ||
| 210 | * - Empty page slabs can purge every dirty page they contain in a | ||
| 211 | * single call, which is not usually the case. | ||
| 212 | * | ||
| 213 | * We purge hugeified empty slabs before nonhugeified ones, on the basis | ||
| 214 | * that they are fully dirty, while nonhugified slabs might not be, so | ||
| 215 | * we free up more pages more easily. | ||
| 216 | */ | ||
| 217 | if (hpdata_nactive_get(ps) == 0) { | ||
| 218 | if (hpdata_huge_get(ps)) { | ||
| 219 | return PSSET_NPURGE_LISTS - 1; | ||
| 220 | } else { | ||
| 221 | return PSSET_NPURGE_LISTS - 2; | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | pszind_t pind = sz_psz2ind(sz_psz_quantize_floor(ndirty << LG_PAGE)); | ||
| 226 | /* | ||
| 227 | * For non-empty slabs, we may reuse them again. Prefer purging | ||
| 228 | * non-hugeified slabs before hugeified ones then, among pages of | ||
| 229 | * similar dirtiness. We still get some benefit from the hugification. | ||
| 230 | */ | ||
| 231 | return (size_t)pind * 2 + (hpdata_huge_get(ps) ? 0 : 1); | ||
| 232 | } | ||
| 233 | |||
| 234 | static void | ||
| 235 | psset_maybe_remove_purge_list(psset_t *psset, hpdata_t *ps) { | ||
| 236 | /* | ||
| 237 | * Remove the hpdata from its purge list (if it's in one). Even if it's | ||
| 238 | * going to stay in the same one, by appending it during | ||
| 239 | * psset_update_end, we move it to the end of its queue, so that we | ||
| 240 | * purge LRU within a given dirtiness bucket. | ||
| 241 | */ | ||
| 242 | if (hpdata_purge_allowed_get(ps)) { | ||
| 243 | size_t ind = psset_purge_list_ind(ps); | ||
| 244 | hpdata_purge_list_t *purge_list = &psset->to_purge[ind]; | ||
| 245 | hpdata_purge_list_remove(purge_list, ps); | ||
| 246 | if (hpdata_purge_list_empty(purge_list)) { | ||
| 247 | fb_unset(psset->purge_bitmap, PSSET_NPURGE_LISTS, ind); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | static void | ||
| 253 | psset_maybe_insert_purge_list(psset_t *psset, hpdata_t *ps) { | ||
| 254 | if (hpdata_purge_allowed_get(ps)) { | ||
| 255 | size_t ind = psset_purge_list_ind(ps); | ||
| 256 | hpdata_purge_list_t *purge_list = &psset->to_purge[ind]; | ||
| 257 | if (hpdata_purge_list_empty(purge_list)) { | ||
| 258 | fb_set(psset->purge_bitmap, PSSET_NPURGE_LISTS, ind); | ||
| 259 | } | ||
| 260 | hpdata_purge_list_append(purge_list, ps); | ||
| 261 | } | ||
| 262 | |||
| 263 | } | ||
| 264 | |||
| 265 | void | ||
| 266 | psset_update_begin(psset_t *psset, hpdata_t *ps) { | ||
| 267 | hpdata_assert_consistent(ps); | ||
| 268 | assert(hpdata_in_psset_get(ps)); | ||
| 269 | hpdata_updating_set(ps, true); | ||
| 270 | psset_stats_remove(psset, ps); | ||
| 271 | if (hpdata_in_psset_alloc_container_get(ps)) { | ||
| 272 | /* | ||
| 273 | * Some metadata updates can break alloc container invariants | ||
| 274 | * (e.g. the longest free range determines the hpdata_heap_t the | ||
| 275 | * pageslab lives in). | ||
| 276 | */ | ||
| 277 | assert(hpdata_alloc_allowed_get(ps)); | ||
| 278 | psset_alloc_container_remove(psset, ps); | ||
| 279 | } | ||
| 280 | psset_maybe_remove_purge_list(psset, ps); | ||
| 281 | /* | ||
| 282 | * We don't update presence in the hugify list; we try to keep it FIFO, | ||
| 283 | * even in the presence of other metadata updates. We'll update | ||
| 284 | * presence at the end of the metadata update if necessary. | ||
| 285 | */ | ||
| 286 | } | ||
| 287 | |||
| 288 | void | ||
| 289 | psset_update_end(psset_t *psset, hpdata_t *ps) { | ||
| 290 | assert(hpdata_in_psset_get(ps)); | ||
| 291 | hpdata_updating_set(ps, false); | ||
| 292 | psset_stats_insert(psset, ps); | ||
| 293 | |||
| 294 | /* | ||
| 295 | * The update begin should have removed ps from whatever alloc container | ||
| 296 | * it was in. | ||
| 297 | */ | ||
| 298 | assert(!hpdata_in_psset_alloc_container_get(ps)); | ||
| 299 | if (hpdata_alloc_allowed_get(ps)) { | ||
| 300 | psset_alloc_container_insert(psset, ps); | ||
| 301 | } | ||
| 302 | psset_maybe_insert_purge_list(psset, ps); | ||
| 303 | |||
| 304 | if (hpdata_hugify_allowed_get(ps) | ||
| 305 | && !hpdata_in_psset_hugify_container_get(ps)) { | ||
| 306 | hpdata_in_psset_hugify_container_set(ps, true); | ||
| 307 | hpdata_hugify_list_append(&psset->to_hugify, ps); | ||
| 308 | } else if (!hpdata_hugify_allowed_get(ps) | ||
| 309 | && hpdata_in_psset_hugify_container_get(ps)) { | ||
| 310 | hpdata_in_psset_hugify_container_set(ps, false); | ||
| 311 | hpdata_hugify_list_remove(&psset->to_hugify, ps); | ||
| 312 | } | ||
| 313 | hpdata_assert_consistent(ps); | ||
| 314 | } | ||
| 315 | |||
| 316 | hpdata_t * | ||
| 317 | psset_pick_alloc(psset_t *psset, size_t size) { | ||
| 318 | assert((size & PAGE_MASK) == 0); | ||
| 319 | assert(size <= HUGEPAGE); | ||
| 320 | |||
| 321 | pszind_t min_pind = sz_psz2ind(sz_psz_quantize_ceil(size)); | ||
| 322 | pszind_t pind = (pszind_t)fb_ffs(psset->pageslab_bitmap, PSSET_NPSIZES, | ||
| 323 | (size_t)min_pind); | ||
| 324 | if (pind == PSSET_NPSIZES) { | ||
| 325 | return hpdata_empty_list_first(&psset->empty); | ||
| 326 | } | ||
| 327 | hpdata_t *ps = hpdata_age_heap_first(&psset->pageslabs[pind]); | ||
| 328 | if (ps == NULL) { | ||
| 329 | return NULL; | ||
| 330 | } | ||
| 331 | |||
| 332 | hpdata_assert_consistent(ps); | ||
| 333 | |||
| 334 | return ps; | ||
| 335 | } | ||
| 336 | |||
| 337 | hpdata_t * | ||
| 338 | psset_pick_purge(psset_t *psset) { | ||
| 339 | ssize_t ind_ssz = fb_fls(psset->purge_bitmap, PSSET_NPURGE_LISTS, | ||
| 340 | PSSET_NPURGE_LISTS - 1); | ||
| 341 | if (ind_ssz < 0) { | ||
| 342 | return NULL; | ||
| 343 | } | ||
| 344 | pszind_t ind = (pszind_t)ind_ssz; | ||
| 345 | assert(ind < PSSET_NPURGE_LISTS); | ||
| 346 | hpdata_t *ps = hpdata_purge_list_first(&psset->to_purge[ind]); | ||
| 347 | assert(ps != NULL); | ||
| 348 | return ps; | ||
| 349 | } | ||
| 350 | |||
| 351 | hpdata_t * | ||
| 352 | psset_pick_hugify(psset_t *psset) { | ||
| 353 | return hpdata_hugify_list_first(&psset->to_hugify); | ||
| 354 | } | ||
| 355 | |||
| 356 | void | ||
| 357 | psset_insert(psset_t *psset, hpdata_t *ps) { | ||
| 358 | hpdata_in_psset_set(ps, true); | ||
| 359 | |||
| 360 | psset_stats_insert(psset, ps); | ||
| 361 | if (hpdata_alloc_allowed_get(ps)) { | ||
| 362 | psset_alloc_container_insert(psset, ps); | ||
| 363 | } | ||
| 364 | psset_maybe_insert_purge_list(psset, ps); | ||
| 365 | |||
| 366 | if (hpdata_hugify_allowed_get(ps)) { | ||
| 367 | hpdata_in_psset_hugify_container_set(ps, true); | ||
| 368 | hpdata_hugify_list_append(&psset->to_hugify, ps); | ||
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 | void | ||
| 373 | psset_remove(psset_t *psset, hpdata_t *ps) { | ||
| 374 | hpdata_in_psset_set(ps, false); | ||
| 375 | |||
| 376 | psset_stats_remove(psset, ps); | ||
| 377 | if (hpdata_in_psset_alloc_container_get(ps)) { | ||
| 378 | psset_alloc_container_remove(psset, ps); | ||
| 379 | } | ||
| 380 | psset_maybe_remove_purge_list(psset, ps); | ||
| 381 | if (hpdata_in_psset_hugify_container_get(ps)) { | ||
| 382 | hpdata_in_psset_hugify_container_set(ps, false); | ||
| 383 | hpdata_hugify_list_remove(&psset->to_hugify, ps); | ||
| 384 | } | ||
| 385 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/rtree.c b/examples/redis-unstable/deps/jemalloc/src/rtree.c deleted file mode 100644 index 6496b5a..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/rtree.c +++ /dev/null | |||
| @@ -1,261 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/mutex.h" | ||
| 6 | |||
| 7 | /* | ||
| 8 | * Only the most significant bits of keys passed to rtree_{read,write}() are | ||
| 9 | * used. | ||
| 10 | */ | ||
| 11 | bool | ||
| 12 | rtree_new(rtree_t *rtree, base_t *base, bool zeroed) { | ||
| 13 | #ifdef JEMALLOC_JET | ||
| 14 | if (!zeroed) { | ||
| 15 | memset(rtree, 0, sizeof(rtree_t)); /* Clear root. */ | ||
| 16 | } | ||
| 17 | #else | ||
| 18 | assert(zeroed); | ||
| 19 | #endif | ||
| 20 | rtree->base = base; | ||
| 21 | |||
| 22 | if (malloc_mutex_init(&rtree->init_lock, "rtree", WITNESS_RANK_RTREE, | ||
| 23 | malloc_mutex_rank_exclusive)) { | ||
| 24 | return true; | ||
| 25 | } | ||
| 26 | |||
| 27 | return false; | ||
| 28 | } | ||
| 29 | |||
| 30 | static rtree_node_elm_t * | ||
| 31 | rtree_node_alloc(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) { | ||
| 32 | return (rtree_node_elm_t *)base_alloc(tsdn, rtree->base, | ||
| 33 | nelms * sizeof(rtree_node_elm_t), CACHELINE); | ||
| 34 | } | ||
| 35 | |||
| 36 | static rtree_leaf_elm_t * | ||
| 37 | rtree_leaf_alloc(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) { | ||
| 38 | return (rtree_leaf_elm_t *)base_alloc(tsdn, rtree->base, | ||
| 39 | nelms * sizeof(rtree_leaf_elm_t), CACHELINE); | ||
| 40 | } | ||
| 41 | |||
| 42 | static rtree_node_elm_t * | ||
| 43 | rtree_node_init(tsdn_t *tsdn, rtree_t *rtree, unsigned level, | ||
| 44 | atomic_p_t *elmp) { | ||
| 45 | malloc_mutex_lock(tsdn, &rtree->init_lock); | ||
| 46 | /* | ||
| 47 | * If *elmp is non-null, then it was initialized with the init lock | ||
| 48 | * held, so we can get by with 'relaxed' here. | ||
| 49 | */ | ||
| 50 | rtree_node_elm_t *node = atomic_load_p(elmp, ATOMIC_RELAXED); | ||
| 51 | if (node == NULL) { | ||
| 52 | node = rtree_node_alloc(tsdn, rtree, ZU(1) << | ||
| 53 | rtree_levels[level].bits); | ||
| 54 | if (node == NULL) { | ||
| 55 | malloc_mutex_unlock(tsdn, &rtree->init_lock); | ||
| 56 | return NULL; | ||
| 57 | } | ||
| 58 | /* | ||
| 59 | * Even though we hold the lock, a later reader might not; we | ||
| 60 | * need release semantics. | ||
| 61 | */ | ||
| 62 | atomic_store_p(elmp, node, ATOMIC_RELEASE); | ||
| 63 | } | ||
| 64 | malloc_mutex_unlock(tsdn, &rtree->init_lock); | ||
| 65 | |||
| 66 | return node; | ||
| 67 | } | ||
| 68 | |||
| 69 | static rtree_leaf_elm_t * | ||
| 70 | rtree_leaf_init(tsdn_t *tsdn, rtree_t *rtree, atomic_p_t *elmp) { | ||
| 71 | malloc_mutex_lock(tsdn, &rtree->init_lock); | ||
| 72 | /* | ||
| 73 | * If *elmp is non-null, then it was initialized with the init lock | ||
| 74 | * held, so we can get by with 'relaxed' here. | ||
| 75 | */ | ||
| 76 | rtree_leaf_elm_t *leaf = atomic_load_p(elmp, ATOMIC_RELAXED); | ||
| 77 | if (leaf == NULL) { | ||
| 78 | leaf = rtree_leaf_alloc(tsdn, rtree, ZU(1) << | ||
| 79 | rtree_levels[RTREE_HEIGHT-1].bits); | ||
| 80 | if (leaf == NULL) { | ||
| 81 | malloc_mutex_unlock(tsdn, &rtree->init_lock); | ||
| 82 | return NULL; | ||
| 83 | } | ||
| 84 | /* | ||
| 85 | * Even though we hold the lock, a later reader might not; we | ||
| 86 | * need release semantics. | ||
| 87 | */ | ||
| 88 | atomic_store_p(elmp, leaf, ATOMIC_RELEASE); | ||
| 89 | } | ||
| 90 | malloc_mutex_unlock(tsdn, &rtree->init_lock); | ||
| 91 | |||
| 92 | return leaf; | ||
| 93 | } | ||
| 94 | |||
| 95 | static bool | ||
| 96 | rtree_node_valid(rtree_node_elm_t *node) { | ||
| 97 | return ((uintptr_t)node != (uintptr_t)0); | ||
| 98 | } | ||
| 99 | |||
| 100 | static bool | ||
| 101 | rtree_leaf_valid(rtree_leaf_elm_t *leaf) { | ||
| 102 | return ((uintptr_t)leaf != (uintptr_t)0); | ||
| 103 | } | ||
| 104 | |||
| 105 | static rtree_node_elm_t * | ||
| 106 | rtree_child_node_tryread(rtree_node_elm_t *elm, bool dependent) { | ||
| 107 | rtree_node_elm_t *node; | ||
| 108 | |||
| 109 | if (dependent) { | ||
| 110 | node = (rtree_node_elm_t *)atomic_load_p(&elm->child, | ||
| 111 | ATOMIC_RELAXED); | ||
| 112 | } else { | ||
| 113 | node = (rtree_node_elm_t *)atomic_load_p(&elm->child, | ||
| 114 | ATOMIC_ACQUIRE); | ||
| 115 | } | ||
| 116 | |||
| 117 | assert(!dependent || node != NULL); | ||
| 118 | return node; | ||
| 119 | } | ||
| 120 | |||
| 121 | static rtree_node_elm_t * | ||
| 122 | rtree_child_node_read(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *elm, | ||
| 123 | unsigned level, bool dependent) { | ||
| 124 | rtree_node_elm_t *node; | ||
| 125 | |||
| 126 | node = rtree_child_node_tryread(elm, dependent); | ||
| 127 | if (!dependent && unlikely(!rtree_node_valid(node))) { | ||
| 128 | node = rtree_node_init(tsdn, rtree, level + 1, &elm->child); | ||
| 129 | } | ||
| 130 | assert(!dependent || node != NULL); | ||
| 131 | return node; | ||
| 132 | } | ||
| 133 | |||
| 134 | static rtree_leaf_elm_t * | ||
| 135 | rtree_child_leaf_tryread(rtree_node_elm_t *elm, bool dependent) { | ||
| 136 | rtree_leaf_elm_t *leaf; | ||
| 137 | |||
| 138 | if (dependent) { | ||
| 139 | leaf = (rtree_leaf_elm_t *)atomic_load_p(&elm->child, | ||
| 140 | ATOMIC_RELAXED); | ||
| 141 | } else { | ||
| 142 | leaf = (rtree_leaf_elm_t *)atomic_load_p(&elm->child, | ||
| 143 | ATOMIC_ACQUIRE); | ||
| 144 | } | ||
| 145 | |||
| 146 | assert(!dependent || leaf != NULL); | ||
| 147 | return leaf; | ||
| 148 | } | ||
| 149 | |||
| 150 | static rtree_leaf_elm_t * | ||
| 151 | rtree_child_leaf_read(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *elm, | ||
| 152 | unsigned level, bool dependent) { | ||
| 153 | rtree_leaf_elm_t *leaf; | ||
| 154 | |||
| 155 | leaf = rtree_child_leaf_tryread(elm, dependent); | ||
| 156 | if (!dependent && unlikely(!rtree_leaf_valid(leaf))) { | ||
| 157 | leaf = rtree_leaf_init(tsdn, rtree, &elm->child); | ||
| 158 | } | ||
| 159 | assert(!dependent || leaf != NULL); | ||
| 160 | return leaf; | ||
| 161 | } | ||
| 162 | |||
| 163 | rtree_leaf_elm_t * | ||
| 164 | rtree_leaf_elm_lookup_hard(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, | ||
| 165 | uintptr_t key, bool dependent, bool init_missing) { | ||
| 166 | rtree_node_elm_t *node; | ||
| 167 | rtree_leaf_elm_t *leaf; | ||
| 168 | #if RTREE_HEIGHT > 1 | ||
| 169 | node = rtree->root; | ||
| 170 | #else | ||
| 171 | leaf = rtree->root; | ||
| 172 | #endif | ||
| 173 | |||
| 174 | if (config_debug) { | ||
| 175 | uintptr_t leafkey = rtree_leafkey(key); | ||
| 176 | for (unsigned i = 0; i < RTREE_CTX_NCACHE; i++) { | ||
| 177 | assert(rtree_ctx->cache[i].leafkey != leafkey); | ||
| 178 | } | ||
| 179 | for (unsigned i = 0; i < RTREE_CTX_NCACHE_L2; i++) { | ||
| 180 | assert(rtree_ctx->l2_cache[i].leafkey != leafkey); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | #define RTREE_GET_CHILD(level) { \ | ||
| 185 | assert(level < RTREE_HEIGHT-1); \ | ||
| 186 | if (level != 0 && !dependent && \ | ||
| 187 | unlikely(!rtree_node_valid(node))) { \ | ||
| 188 | return NULL; \ | ||
| 189 | } \ | ||
| 190 | uintptr_t subkey = rtree_subkey(key, level); \ | ||
| 191 | if (level + 2 < RTREE_HEIGHT) { \ | ||
| 192 | node = init_missing ? \ | ||
| 193 | rtree_child_node_read(tsdn, rtree, \ | ||
| 194 | &node[subkey], level, dependent) : \ | ||
| 195 | rtree_child_node_tryread(&node[subkey], \ | ||
| 196 | dependent); \ | ||
| 197 | } else { \ | ||
| 198 | leaf = init_missing ? \ | ||
| 199 | rtree_child_leaf_read(tsdn, rtree, \ | ||
| 200 | &node[subkey], level, dependent) : \ | ||
| 201 | rtree_child_leaf_tryread(&node[subkey], \ | ||
| 202 | dependent); \ | ||
| 203 | } \ | ||
| 204 | } | ||
| 205 | /* | ||
| 206 | * Cache replacement upon hard lookup (i.e. L1 & L2 rtree cache miss): | ||
| 207 | * (1) evict last entry in L2 cache; (2) move the collision slot from L1 | ||
| 208 | * cache down to L2; and 3) fill L1. | ||
| 209 | */ | ||
| 210 | #define RTREE_GET_LEAF(level) { \ | ||
| 211 | assert(level == RTREE_HEIGHT-1); \ | ||
| 212 | if (!dependent && unlikely(!rtree_leaf_valid(leaf))) { \ | ||
| 213 | return NULL; \ | ||
| 214 | } \ | ||
| 215 | if (RTREE_CTX_NCACHE_L2 > 1) { \ | ||
| 216 | memmove(&rtree_ctx->l2_cache[1], \ | ||
| 217 | &rtree_ctx->l2_cache[0], \ | ||
| 218 | sizeof(rtree_ctx_cache_elm_t) * \ | ||
| 219 | (RTREE_CTX_NCACHE_L2 - 1)); \ | ||
| 220 | } \ | ||
| 221 | size_t slot = rtree_cache_direct_map(key); \ | ||
| 222 | rtree_ctx->l2_cache[0].leafkey = \ | ||
| 223 | rtree_ctx->cache[slot].leafkey; \ | ||
| 224 | rtree_ctx->l2_cache[0].leaf = \ | ||
| 225 | rtree_ctx->cache[slot].leaf; \ | ||
| 226 | uintptr_t leafkey = rtree_leafkey(key); \ | ||
| 227 | rtree_ctx->cache[slot].leafkey = leafkey; \ | ||
| 228 | rtree_ctx->cache[slot].leaf = leaf; \ | ||
| 229 | uintptr_t subkey = rtree_subkey(key, level); \ | ||
| 230 | return &leaf[subkey]; \ | ||
| 231 | } | ||
| 232 | if (RTREE_HEIGHT > 1) { | ||
| 233 | RTREE_GET_CHILD(0) | ||
| 234 | } | ||
| 235 | if (RTREE_HEIGHT > 2) { | ||
| 236 | RTREE_GET_CHILD(1) | ||
| 237 | } | ||
| 238 | if (RTREE_HEIGHT > 3) { | ||
| 239 | for (unsigned i = 2; i < RTREE_HEIGHT-1; i++) { | ||
| 240 | RTREE_GET_CHILD(i) | ||
| 241 | } | ||
| 242 | } | ||
| 243 | RTREE_GET_LEAF(RTREE_HEIGHT-1) | ||
| 244 | #undef RTREE_GET_CHILD | ||
| 245 | #undef RTREE_GET_LEAF | ||
| 246 | not_reached(); | ||
| 247 | } | ||
| 248 | |||
| 249 | void | ||
| 250 | rtree_ctx_data_init(rtree_ctx_t *ctx) { | ||
| 251 | for (unsigned i = 0; i < RTREE_CTX_NCACHE; i++) { | ||
| 252 | rtree_ctx_cache_elm_t *cache = &ctx->cache[i]; | ||
| 253 | cache->leafkey = RTREE_LEAFKEY_INVALID; | ||
| 254 | cache->leaf = NULL; | ||
| 255 | } | ||
| 256 | for (unsigned i = 0; i < RTREE_CTX_NCACHE_L2; i++) { | ||
| 257 | rtree_ctx_cache_elm_t *cache = &ctx->l2_cache[i]; | ||
| 258 | cache->leafkey = RTREE_LEAFKEY_INVALID; | ||
| 259 | cache->leaf = NULL; | ||
| 260 | } | ||
| 261 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/safety_check.c b/examples/redis-unstable/deps/jemalloc/src/safety_check.c deleted file mode 100644 index 209fdda..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/safety_check.c +++ /dev/null | |||
| @@ -1,36 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | static safety_check_abort_hook_t safety_check_abort; | ||
| 5 | |||
| 6 | void safety_check_fail_sized_dealloc(bool current_dealloc, const void *ptr, | ||
| 7 | size_t true_size, size_t input_size) { | ||
| 8 | char *src = current_dealloc ? "the current pointer being freed" : | ||
| 9 | "in thread cache, possibly from previous deallocations"; | ||
| 10 | |||
| 11 | safety_check_fail("<jemalloc>: size mismatch detected (true size %zu " | ||
| 12 | "vs input size %zu), likely caused by application sized " | ||
| 13 | "deallocation bugs (source address: %p, %s). Suggest building with " | ||
| 14 | "--enable-debug or address sanitizer for debugging. Abort.\n", | ||
| 15 | true_size, input_size, ptr, src); | ||
| 16 | } | ||
| 17 | |||
| 18 | void safety_check_set_abort(safety_check_abort_hook_t abort_fn) { | ||
| 19 | safety_check_abort = abort_fn; | ||
| 20 | } | ||
| 21 | |||
| 22 | void safety_check_fail(const char *format, ...) { | ||
| 23 | char buf[MALLOC_PRINTF_BUFSIZE]; | ||
| 24 | |||
| 25 | va_list ap; | ||
| 26 | va_start(ap, format); | ||
| 27 | malloc_vsnprintf(buf, MALLOC_PRINTF_BUFSIZE, format, ap); | ||
| 28 | va_end(ap); | ||
| 29 | |||
| 30 | if (safety_check_abort == NULL) { | ||
| 31 | malloc_write(buf); | ||
| 32 | abort(); | ||
| 33 | } else { | ||
| 34 | safety_check_abort(buf); | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/san.c b/examples/redis-unstable/deps/jemalloc/src/san.c deleted file mode 100644 index 6e51291..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/san.c +++ /dev/null | |||
| @@ -1,208 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/ehooks.h" | ||
| 6 | #include "jemalloc/internal/san.h" | ||
| 7 | #include "jemalloc/internal/tsd.h" | ||
| 8 | |||
| 9 | /* The sanitizer options. */ | ||
| 10 | size_t opt_san_guard_large = SAN_GUARD_LARGE_EVERY_N_EXTENTS_DEFAULT; | ||
| 11 | size_t opt_san_guard_small = SAN_GUARD_SMALL_EVERY_N_EXTENTS_DEFAULT; | ||
| 12 | |||
| 13 | /* Aligned (-1 is off) ptrs will be junked & stashed on dealloc. */ | ||
| 14 | ssize_t opt_lg_san_uaf_align = SAN_LG_UAF_ALIGN_DEFAULT; | ||
| 15 | |||
| 16 | /* | ||
| 17 | * Initialized in san_init(). When disabled, the mask is set to (uintptr_t)-1 | ||
| 18 | * to always fail the nonfast_align check. | ||
| 19 | */ | ||
| 20 | uintptr_t san_cache_bin_nonfast_mask = SAN_CACHE_BIN_NONFAST_MASK_DEFAULT; | ||
| 21 | |||
| 22 | static inline void | ||
| 23 | san_find_guarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2, | ||
| 24 | uintptr_t *addr, size_t size, bool left, bool right) { | ||
| 25 | assert(!edata_guarded_get(edata)); | ||
| 26 | assert(size % PAGE == 0); | ||
| 27 | *addr = (uintptr_t)edata_base_get(edata); | ||
| 28 | if (left) { | ||
| 29 | *guard1 = *addr; | ||
| 30 | *addr += SAN_PAGE_GUARD; | ||
| 31 | } else { | ||
| 32 | *guard1 = 0; | ||
| 33 | } | ||
| 34 | |||
| 35 | if (right) { | ||
| 36 | *guard2 = *addr + size; | ||
| 37 | } else { | ||
| 38 | *guard2 = 0; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | static inline void | ||
| 43 | san_find_unguarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2, | ||
| 44 | uintptr_t *addr, size_t size, bool left, bool right) { | ||
| 45 | assert(edata_guarded_get(edata)); | ||
| 46 | assert(size % PAGE == 0); | ||
| 47 | *addr = (uintptr_t)edata_base_get(edata); | ||
| 48 | if (right) { | ||
| 49 | *guard2 = *addr + size; | ||
| 50 | } else { | ||
| 51 | *guard2 = 0; | ||
| 52 | } | ||
| 53 | |||
| 54 | if (left) { | ||
| 55 | *guard1 = *addr - SAN_PAGE_GUARD; | ||
| 56 | assert(*guard1 != 0); | ||
| 57 | *addr = *guard1; | ||
| 58 | } else { | ||
| 59 | *guard1 = 0; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | void | ||
| 64 | san_guard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap, | ||
| 65 | bool left, bool right, bool remap) { | ||
| 66 | assert(left || right); | ||
| 67 | if (remap) { | ||
| 68 | emap_deregister_boundary(tsdn, emap, edata); | ||
| 69 | } | ||
| 70 | |||
| 71 | size_t size_with_guards = edata_size_get(edata); | ||
| 72 | size_t usize = (left && right) | ||
| 73 | ? san_two_side_unguarded_sz(size_with_guards) | ||
| 74 | : san_one_side_unguarded_sz(size_with_guards); | ||
| 75 | |||
| 76 | uintptr_t guard1, guard2, addr; | ||
| 77 | san_find_guarded_addr(edata, &guard1, &guard2, &addr, usize, left, | ||
| 78 | right); | ||
| 79 | |||
| 80 | assert(edata_state_get(edata) == extent_state_active); | ||
| 81 | ehooks_guard(tsdn, ehooks, (void *)guard1, (void *)guard2); | ||
| 82 | |||
| 83 | /* Update the guarded addr and usable size of the edata. */ | ||
| 84 | edata_size_set(edata, usize); | ||
| 85 | edata_addr_set(edata, (void *)addr); | ||
| 86 | edata_guarded_set(edata, true); | ||
| 87 | |||
| 88 | if (remap) { | ||
| 89 | emap_register_boundary(tsdn, emap, edata, SC_NSIZES, | ||
| 90 | /* slab */ false); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | static void | ||
| 95 | san_unguard_pages_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 96 | emap_t *emap, bool left, bool right, bool remap) { | ||
| 97 | assert(left || right); | ||
| 98 | /* Remove the inner boundary which no longer exists. */ | ||
| 99 | if (remap) { | ||
| 100 | assert(edata_state_get(edata) == extent_state_active); | ||
| 101 | emap_deregister_boundary(tsdn, emap, edata); | ||
| 102 | } else { | ||
| 103 | assert(edata_state_get(edata) == extent_state_retained); | ||
| 104 | } | ||
| 105 | |||
| 106 | size_t size = edata_size_get(edata); | ||
| 107 | size_t size_with_guards = (left && right) | ||
| 108 | ? san_two_side_guarded_sz(size) | ||
| 109 | : san_one_side_guarded_sz(size); | ||
| 110 | |||
| 111 | uintptr_t guard1, guard2, addr; | ||
| 112 | san_find_unguarded_addr(edata, &guard1, &guard2, &addr, size, left, | ||
| 113 | right); | ||
| 114 | |||
| 115 | ehooks_unguard(tsdn, ehooks, (void *)guard1, (void *)guard2); | ||
| 116 | |||
| 117 | /* Update the true addr and usable size of the edata. */ | ||
| 118 | edata_size_set(edata, size_with_guards); | ||
| 119 | edata_addr_set(edata, (void *)addr); | ||
| 120 | edata_guarded_set(edata, false); | ||
| 121 | |||
| 122 | /* | ||
| 123 | * Then re-register the outer boundary including the guards, if | ||
| 124 | * requested. | ||
| 125 | */ | ||
| 126 | if (remap) { | ||
| 127 | emap_register_boundary(tsdn, emap, edata, SC_NSIZES, | ||
| 128 | /* slab */ false); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | void | ||
| 133 | san_unguard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 134 | emap_t *emap, bool left, bool right) { | ||
| 135 | san_unguard_pages_impl(tsdn, ehooks, edata, emap, left, right, | ||
| 136 | /* remap */ true); | ||
| 137 | } | ||
| 138 | |||
| 139 | void | ||
| 140 | san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, | ||
| 141 | emap_t *emap) { | ||
| 142 | emap_assert_not_mapped(tsdn, emap, edata); | ||
| 143 | /* | ||
| 144 | * We don't want to touch the emap of about to be destroyed extents, as | ||
| 145 | * they have been unmapped upon eviction from the retained ecache. Also, | ||
| 146 | * we unguard the extents to the right, because retained extents only | ||
| 147 | * own their right guard page per san_bump_alloc's logic. | ||
| 148 | */ | ||
| 149 | san_unguard_pages_impl(tsdn, ehooks, edata, emap, /* left */ false, | ||
| 150 | /* right */ true, /* remap */ false); | ||
| 151 | } | ||
| 152 | |||
| 153 | static bool | ||
| 154 | san_stashed_corrupted(void *ptr, size_t size) { | ||
| 155 | if (san_junk_ptr_should_slow()) { | ||
| 156 | for (size_t i = 0; i < size; i++) { | ||
| 157 | if (((char *)ptr)[i] != (char)uaf_detect_junk) { | ||
| 158 | return true; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | return false; | ||
| 162 | } | ||
| 163 | |||
| 164 | void *first, *mid, *last; | ||
| 165 | san_junk_ptr_locations(ptr, size, &first, &mid, &last); | ||
| 166 | if (*(uintptr_t *)first != uaf_detect_junk || | ||
| 167 | *(uintptr_t *)mid != uaf_detect_junk || | ||
| 168 | *(uintptr_t *)last != uaf_detect_junk) { | ||
| 169 | return true; | ||
| 170 | } | ||
| 171 | |||
| 172 | return false; | ||
| 173 | } | ||
| 174 | |||
| 175 | void | ||
| 176 | san_check_stashed_ptrs(void **ptrs, size_t nstashed, size_t usize) { | ||
| 177 | /* | ||
| 178 | * Verify that the junked-filled & stashed pointers remain unchanged, to | ||
| 179 | * detect write-after-free. | ||
| 180 | */ | ||
| 181 | for (size_t n = 0; n < nstashed; n++) { | ||
| 182 | void *stashed = ptrs[n]; | ||
| 183 | assert(stashed != NULL); | ||
| 184 | assert(cache_bin_nonfast_aligned(stashed)); | ||
| 185 | if (unlikely(san_stashed_corrupted(stashed, usize))) { | ||
| 186 | safety_check_fail("<jemalloc>: Write-after-free " | ||
| 187 | "detected on deallocated pointer %p (size %zu).\n", | ||
| 188 | stashed, usize); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | void | ||
| 194 | tsd_san_init(tsd_t *tsd) { | ||
| 195 | *tsd_san_extents_until_guard_smallp_get(tsd) = opt_san_guard_small; | ||
| 196 | *tsd_san_extents_until_guard_largep_get(tsd) = opt_san_guard_large; | ||
| 197 | } | ||
| 198 | |||
| 199 | void | ||
| 200 | san_init(ssize_t lg_san_uaf_align) { | ||
| 201 | assert(lg_san_uaf_align == -1 || lg_san_uaf_align >= LG_PAGE); | ||
| 202 | if (lg_san_uaf_align == -1) { | ||
| 203 | san_cache_bin_nonfast_mask = (uintptr_t)-1; | ||
| 204 | return; | ||
| 205 | } | ||
| 206 | |||
| 207 | san_cache_bin_nonfast_mask = ((uintptr_t)1 << lg_san_uaf_align) - 1; | ||
| 208 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/san_bump.c b/examples/redis-unstable/deps/jemalloc/src/san_bump.c deleted file mode 100644 index 8889745..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/san_bump.c +++ /dev/null | |||
| @@ -1,104 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/san_bump.h" | ||
| 5 | #include "jemalloc/internal/pac.h" | ||
| 6 | #include "jemalloc/internal/san.h" | ||
| 7 | #include "jemalloc/internal/ehooks.h" | ||
| 8 | #include "jemalloc/internal/edata_cache.h" | ||
| 9 | |||
| 10 | static bool | ||
| 11 | san_bump_grow_locked(tsdn_t *tsdn, san_bump_alloc_t *sba, pac_t *pac, | ||
| 12 | ehooks_t *ehooks, size_t size); | ||
| 13 | |||
| 14 | edata_t * | ||
| 15 | san_bump_alloc(tsdn_t *tsdn, san_bump_alloc_t* sba, pac_t *pac, | ||
| 16 | ehooks_t *ehooks, size_t size, bool zero) { | ||
| 17 | assert(san_bump_enabled()); | ||
| 18 | |||
| 19 | edata_t* to_destroy; | ||
| 20 | size_t guarded_size = san_one_side_guarded_sz(size); | ||
| 21 | |||
| 22 | malloc_mutex_lock(tsdn, &sba->mtx); | ||
| 23 | |||
| 24 | if (sba->curr_reg == NULL || | ||
| 25 | edata_size_get(sba->curr_reg) < guarded_size) { | ||
| 26 | /* | ||
| 27 | * If the current region can't accommodate the allocation, | ||
| 28 | * try replacing it with a larger one and destroy current if the | ||
| 29 | * replacement succeeds. | ||
| 30 | */ | ||
| 31 | to_destroy = sba->curr_reg; | ||
| 32 | bool err = san_bump_grow_locked(tsdn, sba, pac, ehooks, | ||
| 33 | guarded_size); | ||
| 34 | if (err) { | ||
| 35 | goto label_err; | ||
| 36 | } | ||
| 37 | } else { | ||
| 38 | to_destroy = NULL; | ||
| 39 | } | ||
| 40 | assert(guarded_size <= edata_size_get(sba->curr_reg)); | ||
| 41 | size_t trail_size = edata_size_get(sba->curr_reg) - guarded_size; | ||
| 42 | |||
| 43 | edata_t* edata; | ||
| 44 | if (trail_size != 0) { | ||
| 45 | edata_t* curr_reg_trail = extent_split_wrapper(tsdn, pac, | ||
| 46 | ehooks, sba->curr_reg, guarded_size, trail_size, | ||
| 47 | /* holding_core_locks */ true); | ||
| 48 | if (curr_reg_trail == NULL) { | ||
| 49 | goto label_err; | ||
| 50 | } | ||
| 51 | edata = sba->curr_reg; | ||
| 52 | sba->curr_reg = curr_reg_trail; | ||
| 53 | } else { | ||
| 54 | edata = sba->curr_reg; | ||
| 55 | sba->curr_reg = NULL; | ||
| 56 | } | ||
| 57 | |||
| 58 | malloc_mutex_unlock(tsdn, &sba->mtx); | ||
| 59 | |||
| 60 | assert(!edata_guarded_get(edata)); | ||
| 61 | assert(sba->curr_reg == NULL || !edata_guarded_get(sba->curr_reg)); | ||
| 62 | assert(to_destroy == NULL || !edata_guarded_get(to_destroy)); | ||
| 63 | |||
| 64 | if (to_destroy != NULL) { | ||
| 65 | extent_destroy_wrapper(tsdn, pac, ehooks, to_destroy); | ||
| 66 | } | ||
| 67 | |||
| 68 | san_guard_pages(tsdn, ehooks, edata, pac->emap, /* left */ false, | ||
| 69 | /* right */ true, /* remap */ true); | ||
| 70 | |||
| 71 | if (extent_commit_zero(tsdn, ehooks, edata, /* commit */ true, zero, | ||
| 72 | /* growing_retained */ false)) { | ||
| 73 | extent_record(tsdn, pac, ehooks, &pac->ecache_retained, | ||
| 74 | edata); | ||
| 75 | return NULL; | ||
| 76 | } | ||
| 77 | |||
| 78 | if (config_prof) { | ||
| 79 | extent_gdump_add(tsdn, edata); | ||
| 80 | } | ||
| 81 | |||
| 82 | return edata; | ||
| 83 | label_err: | ||
| 84 | malloc_mutex_unlock(tsdn, &sba->mtx); | ||
| 85 | return NULL; | ||
| 86 | } | ||
| 87 | |||
| 88 | static bool | ||
| 89 | san_bump_grow_locked(tsdn_t *tsdn, san_bump_alloc_t *sba, pac_t *pac, | ||
| 90 | ehooks_t *ehooks, size_t size) { | ||
| 91 | malloc_mutex_assert_owner(tsdn, &sba->mtx); | ||
| 92 | |||
| 93 | bool committed = false, zeroed = false; | ||
| 94 | size_t alloc_size = size > SBA_RETAINED_ALLOC_SIZE ? size : | ||
| 95 | SBA_RETAINED_ALLOC_SIZE; | ||
| 96 | assert((alloc_size & PAGE_MASK) == 0); | ||
| 97 | sba->curr_reg = extent_alloc_wrapper(tsdn, pac, ehooks, NULL, | ||
| 98 | alloc_size, PAGE, zeroed, &committed, | ||
| 99 | /* growing_retained */ true); | ||
| 100 | if (sba->curr_reg == NULL) { | ||
| 101 | return true; | ||
| 102 | } | ||
| 103 | return false; | ||
| 104 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/sc.c b/examples/redis-unstable/deps/jemalloc/src/sc.c deleted file mode 100644 index e4a94d8..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/sc.c +++ /dev/null | |||
| @@ -1,306 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | |||
| 3 | #include "jemalloc/internal/assert.h" | ||
| 4 | #include "jemalloc/internal/bit_util.h" | ||
| 5 | #include "jemalloc/internal/bitmap.h" | ||
| 6 | #include "jemalloc/internal/pages.h" | ||
| 7 | #include "jemalloc/internal/sc.h" | ||
| 8 | |||
| 9 | /* | ||
| 10 | * This module computes the size classes used to satisfy allocations. The logic | ||
| 11 | * here was ported more or less line-by-line from a shell script, and because of | ||
| 12 | * that is not the most idiomatic C. Eventually we should fix this, but for now | ||
| 13 | * at least the damage is compartmentalized to this file. | ||
| 14 | */ | ||
| 15 | |||
| 16 | size_t | ||
| 17 | reg_size_compute(int lg_base, int lg_delta, int ndelta) { | ||
| 18 | return (ZU(1) << lg_base) + (ZU(ndelta) << lg_delta); | ||
| 19 | } | ||
| 20 | |||
| 21 | /* Returns the number of pages in the slab. */ | ||
| 22 | static int | ||
| 23 | slab_size(int lg_page, int lg_base, int lg_delta, int ndelta) { | ||
| 24 | size_t page = (ZU(1) << lg_page); | ||
| 25 | size_t reg_size = reg_size_compute(lg_base, lg_delta, ndelta); | ||
| 26 | |||
| 27 | size_t try_slab_size = page; | ||
| 28 | size_t try_nregs = try_slab_size / reg_size; | ||
| 29 | size_t perfect_slab_size = 0; | ||
| 30 | bool perfect = false; | ||
| 31 | /* | ||
| 32 | * This loop continues until we find the least common multiple of the | ||
| 33 | * page size and size class size. Size classes are all of the form | ||
| 34 | * base + ndelta * delta == (ndelta + base/ndelta) * delta, which is | ||
| 35 | * (ndelta + ngroup) * delta. The way we choose slabbing strategies | ||
| 36 | * means that delta is at most the page size and ndelta < ngroup. So | ||
| 37 | * the loop executes for at most 2 * ngroup - 1 iterations, which is | ||
| 38 | * also the bound on the number of pages in a slab chosen by default. | ||
| 39 | * With the current default settings, this is at most 7. | ||
| 40 | */ | ||
| 41 | while (!perfect) { | ||
| 42 | perfect_slab_size = try_slab_size; | ||
| 43 | size_t perfect_nregs = try_nregs; | ||
| 44 | try_slab_size += page; | ||
| 45 | try_nregs = try_slab_size / reg_size; | ||
| 46 | if (perfect_slab_size == perfect_nregs * reg_size) { | ||
| 47 | perfect = true; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | return (int)(perfect_slab_size / page); | ||
| 51 | } | ||
| 52 | |||
| 53 | static void | ||
| 54 | size_class( | ||
| 55 | /* Output. */ | ||
| 56 | sc_t *sc, | ||
| 57 | /* Configuration decisions. */ | ||
| 58 | int lg_max_lookup, int lg_page, int lg_ngroup, | ||
| 59 | /* Inputs specific to the size class. */ | ||
| 60 | int index, int lg_base, int lg_delta, int ndelta) { | ||
| 61 | sc->index = index; | ||
| 62 | sc->lg_base = lg_base; | ||
| 63 | sc->lg_delta = lg_delta; | ||
| 64 | sc->ndelta = ndelta; | ||
| 65 | size_t size = reg_size_compute(lg_base, lg_delta, ndelta); | ||
| 66 | sc->psz = (size % (ZU(1) << lg_page) == 0); | ||
| 67 | if (index == 0) { | ||
| 68 | assert(!sc->psz); | ||
| 69 | } | ||
| 70 | if (size < (ZU(1) << (lg_page + lg_ngroup))) { | ||
| 71 | sc->bin = true; | ||
| 72 | sc->pgs = slab_size(lg_page, lg_base, lg_delta, ndelta); | ||
| 73 | } else { | ||
| 74 | sc->bin = false; | ||
| 75 | sc->pgs = 0; | ||
| 76 | } | ||
| 77 | if (size <= (ZU(1) << lg_max_lookup)) { | ||
| 78 | sc->lg_delta_lookup = lg_delta; | ||
| 79 | } else { | ||
| 80 | sc->lg_delta_lookup = 0; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | static void | ||
| 85 | size_classes( | ||
| 86 | /* Output. */ | ||
| 87 | sc_data_t *sc_data, | ||
| 88 | /* Determined by the system. */ | ||
| 89 | size_t lg_ptr_size, int lg_quantum, | ||
| 90 | /* Configuration decisions. */ | ||
| 91 | int lg_tiny_min, int lg_max_lookup, int lg_page, int lg_ngroup) { | ||
| 92 | int ptr_bits = (1 << lg_ptr_size) * 8; | ||
| 93 | int ngroup = (1 << lg_ngroup); | ||
| 94 | int ntiny = 0; | ||
| 95 | int nlbins = 0; | ||
| 96 | int lg_tiny_maxclass = (unsigned)-1; | ||
| 97 | int nbins = 0; | ||
| 98 | int npsizes = 0; | ||
| 99 | |||
| 100 | int index = 0; | ||
| 101 | |||
| 102 | int ndelta = 0; | ||
| 103 | int lg_base = lg_tiny_min; | ||
| 104 | int lg_delta = lg_base; | ||
| 105 | |||
| 106 | /* Outputs that we update as we go. */ | ||
| 107 | size_t lookup_maxclass = 0; | ||
| 108 | size_t small_maxclass = 0; | ||
| 109 | int lg_large_minclass = 0; | ||
| 110 | size_t large_maxclass = 0; | ||
| 111 | |||
| 112 | /* Tiny size classes. */ | ||
| 113 | while (lg_base < lg_quantum) { | ||
| 114 | sc_t *sc = &sc_data->sc[index]; | ||
| 115 | size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, | ||
| 116 | lg_base, lg_delta, ndelta); | ||
| 117 | if (sc->lg_delta_lookup != 0) { | ||
| 118 | nlbins = index + 1; | ||
| 119 | } | ||
| 120 | if (sc->psz) { | ||
| 121 | npsizes++; | ||
| 122 | } | ||
| 123 | if (sc->bin) { | ||
| 124 | nbins++; | ||
| 125 | } | ||
| 126 | ntiny++; | ||
| 127 | /* Final written value is correct. */ | ||
| 128 | lg_tiny_maxclass = lg_base; | ||
| 129 | index++; | ||
| 130 | lg_delta = lg_base; | ||
| 131 | lg_base++; | ||
| 132 | } | ||
| 133 | |||
| 134 | /* First non-tiny (pseudo) group. */ | ||
| 135 | if (ntiny != 0) { | ||
| 136 | sc_t *sc = &sc_data->sc[index]; | ||
| 137 | /* | ||
| 138 | * See the note in sc.h; the first non-tiny size class has an | ||
| 139 | * unusual encoding. | ||
| 140 | */ | ||
| 141 | lg_base--; | ||
| 142 | ndelta = 1; | ||
| 143 | size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, | ||
| 144 | lg_base, lg_delta, ndelta); | ||
| 145 | index++; | ||
| 146 | lg_base++; | ||
| 147 | lg_delta++; | ||
| 148 | if (sc->psz) { | ||
| 149 | npsizes++; | ||
| 150 | } | ||
| 151 | if (sc->bin) { | ||
| 152 | nbins++; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | while (ndelta < ngroup) { | ||
| 156 | sc_t *sc = &sc_data->sc[index]; | ||
| 157 | size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, | ||
| 158 | lg_base, lg_delta, ndelta); | ||
| 159 | index++; | ||
| 160 | ndelta++; | ||
| 161 | if (sc->psz) { | ||
| 162 | npsizes++; | ||
| 163 | } | ||
| 164 | if (sc->bin) { | ||
| 165 | nbins++; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | /* All remaining groups. */ | ||
| 170 | lg_base = lg_base + lg_ngroup; | ||
| 171 | while (lg_base < ptr_bits - 1) { | ||
| 172 | ndelta = 1; | ||
| 173 | int ndelta_limit; | ||
| 174 | if (lg_base == ptr_bits - 2) { | ||
| 175 | ndelta_limit = ngroup - 1; | ||
| 176 | } else { | ||
| 177 | ndelta_limit = ngroup; | ||
| 178 | } | ||
| 179 | while (ndelta <= ndelta_limit) { | ||
| 180 | sc_t *sc = &sc_data->sc[index]; | ||
| 181 | size_class(sc, lg_max_lookup, lg_page, lg_ngroup, index, | ||
| 182 | lg_base, lg_delta, ndelta); | ||
| 183 | if (sc->lg_delta_lookup != 0) { | ||
| 184 | nlbins = index + 1; | ||
| 185 | /* Final written value is correct. */ | ||
| 186 | lookup_maxclass = (ZU(1) << lg_base) | ||
| 187 | + (ZU(ndelta) << lg_delta); | ||
| 188 | } | ||
| 189 | if (sc->psz) { | ||
| 190 | npsizes++; | ||
| 191 | } | ||
| 192 | if (sc->bin) { | ||
| 193 | nbins++; | ||
| 194 | /* Final written value is correct. */ | ||
| 195 | small_maxclass = (ZU(1) << lg_base) | ||
| 196 | + (ZU(ndelta) << lg_delta); | ||
| 197 | if (lg_ngroup > 0) { | ||
| 198 | lg_large_minclass = lg_base + 1; | ||
| 199 | } else { | ||
| 200 | lg_large_minclass = lg_base + 2; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | large_maxclass = (ZU(1) << lg_base) | ||
| 204 | + (ZU(ndelta) << lg_delta); | ||
| 205 | index++; | ||
| 206 | ndelta++; | ||
| 207 | } | ||
| 208 | lg_base++; | ||
| 209 | lg_delta++; | ||
| 210 | } | ||
| 211 | /* Additional outputs. */ | ||
| 212 | int nsizes = index; | ||
| 213 | unsigned lg_ceil_nsizes = lg_ceil(nsizes); | ||
| 214 | |||
| 215 | /* Fill in the output data. */ | ||
| 216 | sc_data->ntiny = ntiny; | ||
| 217 | sc_data->nlbins = nlbins; | ||
| 218 | sc_data->nbins = nbins; | ||
| 219 | sc_data->nsizes = nsizes; | ||
| 220 | sc_data->lg_ceil_nsizes = lg_ceil_nsizes; | ||
| 221 | sc_data->npsizes = npsizes; | ||
| 222 | sc_data->lg_tiny_maxclass = lg_tiny_maxclass; | ||
| 223 | sc_data->lookup_maxclass = lookup_maxclass; | ||
| 224 | sc_data->small_maxclass = small_maxclass; | ||
| 225 | sc_data->lg_large_minclass = lg_large_minclass; | ||
| 226 | sc_data->large_minclass = (ZU(1) << lg_large_minclass); | ||
| 227 | sc_data->large_maxclass = large_maxclass; | ||
| 228 | |||
| 229 | /* | ||
| 230 | * We compute these values in two ways: | ||
| 231 | * - Incrementally, as above. | ||
| 232 | * - In macros, in sc.h. | ||
| 233 | * The computation is easier when done incrementally, but putting it in | ||
| 234 | * a constant makes it available to the fast paths without having to | ||
| 235 | * touch the extra global cacheline. We assert, however, that the two | ||
| 236 | * computations are equivalent. | ||
| 237 | */ | ||
| 238 | assert(sc_data->npsizes == SC_NPSIZES); | ||
| 239 | assert(sc_data->lg_tiny_maxclass == SC_LG_TINY_MAXCLASS); | ||
| 240 | assert(sc_data->small_maxclass == SC_SMALL_MAXCLASS); | ||
| 241 | assert(sc_data->large_minclass == SC_LARGE_MINCLASS); | ||
| 242 | assert(sc_data->lg_large_minclass == SC_LG_LARGE_MINCLASS); | ||
| 243 | assert(sc_data->large_maxclass == SC_LARGE_MAXCLASS); | ||
| 244 | |||
| 245 | /* | ||
| 246 | * In the allocation fastpath, we want to assume that we can | ||
| 247 | * unconditionally subtract the requested allocation size from | ||
| 248 | * a ssize_t, and detect passing through 0 correctly. This | ||
| 249 | * results in optimal generated code. For this to work, the | ||
| 250 | * maximum allocation size must be less than SSIZE_MAX. | ||
| 251 | */ | ||
| 252 | assert(SC_LARGE_MAXCLASS < SSIZE_MAX); | ||
| 253 | } | ||
| 254 | |||
| 255 | void | ||
| 256 | sc_data_init(sc_data_t *sc_data) { | ||
| 257 | size_classes(sc_data, LG_SIZEOF_PTR, LG_QUANTUM, SC_LG_TINY_MIN, | ||
| 258 | SC_LG_MAX_LOOKUP, LG_PAGE, SC_LG_NGROUP); | ||
| 259 | |||
| 260 | sc_data->initialized = true; | ||
| 261 | } | ||
| 262 | |||
| 263 | static void | ||
| 264 | sc_data_update_sc_slab_size(sc_t *sc, size_t reg_size, size_t pgs_guess) { | ||
| 265 | size_t min_pgs = reg_size / PAGE; | ||
| 266 | if (reg_size % PAGE != 0) { | ||
| 267 | min_pgs++; | ||
| 268 | } | ||
| 269 | /* | ||
| 270 | * BITMAP_MAXBITS is actually determined by putting the smallest | ||
| 271 | * possible size-class on one page, so this can never be 0. | ||
| 272 | */ | ||
| 273 | size_t max_pgs = BITMAP_MAXBITS * reg_size / PAGE; | ||
| 274 | |||
| 275 | assert(min_pgs <= max_pgs); | ||
| 276 | assert(min_pgs > 0); | ||
| 277 | assert(max_pgs >= 1); | ||
| 278 | if (pgs_guess < min_pgs) { | ||
| 279 | sc->pgs = (int)min_pgs; | ||
| 280 | } else if (pgs_guess > max_pgs) { | ||
| 281 | sc->pgs = (int)max_pgs; | ||
| 282 | } else { | ||
| 283 | sc->pgs = (int)pgs_guess; | ||
| 284 | } | ||
| 285 | } | ||
| 286 | |||
| 287 | void | ||
| 288 | sc_data_update_slab_size(sc_data_t *data, size_t begin, size_t end, int pgs) { | ||
| 289 | assert(data->initialized); | ||
| 290 | for (int i = 0; i < data->nsizes; i++) { | ||
| 291 | sc_t *sc = &data->sc[i]; | ||
| 292 | if (!sc->bin) { | ||
| 293 | break; | ||
| 294 | } | ||
| 295 | size_t reg_size = reg_size_compute(sc->lg_base, sc->lg_delta, | ||
| 296 | sc->ndelta); | ||
| 297 | if (begin <= reg_size && reg_size <= end) { | ||
| 298 | sc_data_update_sc_slab_size(sc, reg_size, pgs); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | void | ||
| 304 | sc_boot(sc_data_t *data) { | ||
| 305 | sc_data_init(data); | ||
| 306 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/sec.c b/examples/redis-unstable/deps/jemalloc/src/sec.c deleted file mode 100644 index df67559..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/sec.c +++ /dev/null | |||
| @@ -1,422 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/sec.h" | ||
| 5 | |||
| 6 | static edata_t *sec_alloc(tsdn_t *tsdn, pai_t *self, size_t size, | ||
| 7 | size_t alignment, bool zero, bool guarded, bool frequent_reuse, | ||
| 8 | bool *deferred_work_generated); | ||
| 9 | static bool sec_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 10 | size_t old_size, size_t new_size, bool zero, bool *deferred_work_generated); | ||
| 11 | static bool sec_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 12 | size_t old_size, size_t new_size, bool *deferred_work_generated); | ||
| 13 | static void sec_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 14 | bool *deferred_work_generated); | ||
| 15 | |||
| 16 | static void | ||
| 17 | sec_bin_init(sec_bin_t *bin) { | ||
| 18 | bin->being_batch_filled = false; | ||
| 19 | bin->bytes_cur = 0; | ||
| 20 | edata_list_active_init(&bin->freelist); | ||
| 21 | } | ||
| 22 | |||
| 23 | bool | ||
| 24 | sec_init(tsdn_t *tsdn, sec_t *sec, base_t *base, pai_t *fallback, | ||
| 25 | const sec_opts_t *opts) { | ||
| 26 | assert(opts->max_alloc >= PAGE); | ||
| 27 | |||
| 28 | size_t max_alloc = PAGE_FLOOR(opts->max_alloc); | ||
| 29 | pszind_t npsizes = sz_psz2ind(max_alloc) + 1; | ||
| 30 | |||
| 31 | size_t sz_shards = opts->nshards * sizeof(sec_shard_t); | ||
| 32 | size_t sz_bins = opts->nshards * (size_t)npsizes * sizeof(sec_bin_t); | ||
| 33 | size_t sz_alloc = sz_shards + sz_bins; | ||
| 34 | void *dynalloc = base_alloc(tsdn, base, sz_alloc, CACHELINE); | ||
| 35 | if (dynalloc == NULL) { | ||
| 36 | return true; | ||
| 37 | } | ||
| 38 | sec_shard_t *shard_cur = (sec_shard_t *)dynalloc; | ||
| 39 | sec->shards = shard_cur; | ||
| 40 | sec_bin_t *bin_cur = (sec_bin_t *)&shard_cur[opts->nshards]; | ||
| 41 | /* Just for asserts, below. */ | ||
| 42 | sec_bin_t *bin_start = bin_cur; | ||
| 43 | |||
| 44 | for (size_t i = 0; i < opts->nshards; i++) { | ||
| 45 | sec_shard_t *shard = shard_cur; | ||
| 46 | shard_cur++; | ||
| 47 | bool err = malloc_mutex_init(&shard->mtx, "sec_shard", | ||
| 48 | WITNESS_RANK_SEC_SHARD, malloc_mutex_rank_exclusive); | ||
| 49 | if (err) { | ||
| 50 | return true; | ||
| 51 | } | ||
| 52 | shard->enabled = true; | ||
| 53 | shard->bins = bin_cur; | ||
| 54 | for (pszind_t j = 0; j < npsizes; j++) { | ||
| 55 | sec_bin_init(&shard->bins[j]); | ||
| 56 | bin_cur++; | ||
| 57 | } | ||
| 58 | shard->bytes_cur = 0; | ||
| 59 | shard->to_flush_next = 0; | ||
| 60 | } | ||
| 61 | /* | ||
| 62 | * Should have exactly matched the bin_start to the first unused byte | ||
| 63 | * after the shards. | ||
| 64 | */ | ||
| 65 | assert((void *)shard_cur == (void *)bin_start); | ||
| 66 | /* And the last bin to use up the last bytes of the allocation. */ | ||
| 67 | assert((char *)bin_cur == ((char *)dynalloc + sz_alloc)); | ||
| 68 | sec->fallback = fallback; | ||
| 69 | |||
| 70 | |||
| 71 | sec->opts = *opts; | ||
| 72 | sec->npsizes = npsizes; | ||
| 73 | |||
| 74 | /* | ||
| 75 | * Initialize these last so that an improper use of an SEC whose | ||
| 76 | * initialization failed will segfault in an easy-to-spot way. | ||
| 77 | */ | ||
| 78 | sec->pai.alloc = &sec_alloc; | ||
| 79 | sec->pai.alloc_batch = &pai_alloc_batch_default; | ||
| 80 | sec->pai.expand = &sec_expand; | ||
| 81 | sec->pai.shrink = &sec_shrink; | ||
| 82 | sec->pai.dalloc = &sec_dalloc; | ||
| 83 | sec->pai.dalloc_batch = &pai_dalloc_batch_default; | ||
| 84 | |||
| 85 | return false; | ||
| 86 | } | ||
| 87 | |||
| 88 | static sec_shard_t * | ||
| 89 | sec_shard_pick(tsdn_t *tsdn, sec_t *sec) { | ||
| 90 | /* | ||
| 91 | * Eventually, we should implement affinity, tracking source shard using | ||
| 92 | * the edata_t's newly freed up fields. For now, just randomly | ||
| 93 | * distribute across all shards. | ||
| 94 | */ | ||
| 95 | if (tsdn_null(tsdn)) { | ||
| 96 | return &sec->shards[0]; | ||
| 97 | } | ||
| 98 | tsd_t *tsd = tsdn_tsd(tsdn); | ||
| 99 | uint8_t *idxp = tsd_sec_shardp_get(tsd); | ||
| 100 | if (*idxp == (uint8_t)-1) { | ||
| 101 | /* | ||
| 102 | * First use; initialize using the trick from Daniel Lemire's | ||
| 103 | * "A fast alternative to the modulo reduction. Use a 64 bit | ||
| 104 | * number to store 32 bits, since we'll deliberately overflow | ||
| 105 | * when we multiply by the number of shards. | ||
| 106 | */ | ||
| 107 | uint64_t rand32 = prng_lg_range_u64(tsd_prng_statep_get(tsd), 32); | ||
| 108 | uint32_t idx = | ||
| 109 | (uint32_t)((rand32 * (uint64_t)sec->opts.nshards) >> 32); | ||
| 110 | assert(idx < (uint32_t)sec->opts.nshards); | ||
| 111 | *idxp = (uint8_t)idx; | ||
| 112 | } | ||
| 113 | return &sec->shards[*idxp]; | ||
| 114 | } | ||
| 115 | |||
| 116 | /* | ||
| 117 | * Perhaps surprisingly, this can be called on the alloc pathways; if we hit an | ||
| 118 | * empty cache, we'll try to fill it, which can push the shard over it's limit. | ||
| 119 | */ | ||
| 120 | static void | ||
| 121 | sec_flush_some_and_unlock(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard) { | ||
| 122 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 123 | edata_list_active_t to_flush; | ||
| 124 | edata_list_active_init(&to_flush); | ||
| 125 | while (shard->bytes_cur > sec->opts.bytes_after_flush) { | ||
| 126 | /* Pick a victim. */ | ||
| 127 | sec_bin_t *bin = &shard->bins[shard->to_flush_next]; | ||
| 128 | |||
| 129 | /* Update our victim-picking state. */ | ||
| 130 | shard->to_flush_next++; | ||
| 131 | if (shard->to_flush_next == sec->npsizes) { | ||
| 132 | shard->to_flush_next = 0; | ||
| 133 | } | ||
| 134 | |||
| 135 | assert(shard->bytes_cur >= bin->bytes_cur); | ||
| 136 | if (bin->bytes_cur != 0) { | ||
| 137 | shard->bytes_cur -= bin->bytes_cur; | ||
| 138 | bin->bytes_cur = 0; | ||
| 139 | edata_list_active_concat(&to_flush, &bin->freelist); | ||
| 140 | } | ||
| 141 | /* | ||
| 142 | * Either bin->bytes_cur was 0, in which case we didn't touch | ||
| 143 | * the bin list but it should be empty anyways (or else we | ||
| 144 | * missed a bytes_cur update on a list modification), or it | ||
| 145 | * *was* 0 and we emptied it ourselves. Either way, it should | ||
| 146 | * be empty now. | ||
| 147 | */ | ||
| 148 | assert(edata_list_active_empty(&bin->freelist)); | ||
| 149 | } | ||
| 150 | |||
| 151 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 152 | bool deferred_work_generated = false; | ||
| 153 | pai_dalloc_batch(tsdn, sec->fallback, &to_flush, | ||
| 154 | &deferred_work_generated); | ||
| 155 | } | ||
| 156 | |||
| 157 | static edata_t * | ||
| 158 | sec_shard_alloc_locked(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard, | ||
| 159 | sec_bin_t *bin) { | ||
| 160 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 161 | if (!shard->enabled) { | ||
| 162 | return NULL; | ||
| 163 | } | ||
| 164 | edata_t *edata = edata_list_active_first(&bin->freelist); | ||
| 165 | if (edata != NULL) { | ||
| 166 | edata_list_active_remove(&bin->freelist, edata); | ||
| 167 | assert(edata_size_get(edata) <= bin->bytes_cur); | ||
| 168 | bin->bytes_cur -= edata_size_get(edata); | ||
| 169 | assert(edata_size_get(edata) <= shard->bytes_cur); | ||
| 170 | shard->bytes_cur -= edata_size_get(edata); | ||
| 171 | } | ||
| 172 | return edata; | ||
| 173 | } | ||
| 174 | |||
| 175 | static edata_t * | ||
| 176 | sec_batch_fill_and_alloc(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard, | ||
| 177 | sec_bin_t *bin, size_t size) { | ||
| 178 | malloc_mutex_assert_not_owner(tsdn, &shard->mtx); | ||
| 179 | |||
| 180 | edata_list_active_t result; | ||
| 181 | edata_list_active_init(&result); | ||
| 182 | bool deferred_work_generated = false; | ||
| 183 | size_t nalloc = pai_alloc_batch(tsdn, sec->fallback, size, | ||
| 184 | 1 + sec->opts.batch_fill_extra, &result, &deferred_work_generated); | ||
| 185 | |||
| 186 | edata_t *ret = edata_list_active_first(&result); | ||
| 187 | if (ret != NULL) { | ||
| 188 | edata_list_active_remove(&result, ret); | ||
| 189 | } | ||
| 190 | |||
| 191 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 192 | bin->being_batch_filled = false; | ||
| 193 | /* | ||
| 194 | * Handle the easy case first: nothing to cache. Note that this can | ||
| 195 | * only happen in case of OOM, since sec_alloc checks the expected | ||
| 196 | * number of allocs, and doesn't bother going down the batch_fill | ||
| 197 | * pathway if there won't be anything left to cache. So to be in this | ||
| 198 | * code path, we must have asked for > 1 alloc, but only gotten 1 back. | ||
| 199 | */ | ||
| 200 | if (nalloc <= 1) { | ||
| 201 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 202 | return ret; | ||
| 203 | } | ||
| 204 | |||
| 205 | size_t new_cached_bytes = (nalloc - 1) * size; | ||
| 206 | |||
| 207 | edata_list_active_concat(&bin->freelist, &result); | ||
| 208 | bin->bytes_cur += new_cached_bytes; | ||
| 209 | shard->bytes_cur += new_cached_bytes; | ||
| 210 | |||
| 211 | if (shard->bytes_cur > sec->opts.max_bytes) { | ||
| 212 | sec_flush_some_and_unlock(tsdn, sec, shard); | ||
| 213 | } else { | ||
| 214 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 215 | } | ||
| 216 | |||
| 217 | return ret; | ||
| 218 | } | ||
| 219 | |||
| 220 | static edata_t * | ||
| 221 | sec_alloc(tsdn_t *tsdn, pai_t *self, size_t size, size_t alignment, bool zero, | ||
| 222 | bool guarded, bool frequent_reuse, bool *deferred_work_generated) { | ||
| 223 | assert((size & PAGE_MASK) == 0); | ||
| 224 | assert(!guarded); | ||
| 225 | |||
| 226 | sec_t *sec = (sec_t *)self; | ||
| 227 | |||
| 228 | if (zero || alignment > PAGE || sec->opts.nshards == 0 | ||
| 229 | || size > sec->opts.max_alloc) { | ||
| 230 | return pai_alloc(tsdn, sec->fallback, size, alignment, zero, | ||
| 231 | /* guarded */ false, frequent_reuse, | ||
| 232 | deferred_work_generated); | ||
| 233 | } | ||
| 234 | pszind_t pszind = sz_psz2ind(size); | ||
| 235 | assert(pszind < sec->npsizes); | ||
| 236 | |||
| 237 | sec_shard_t *shard = sec_shard_pick(tsdn, sec); | ||
| 238 | sec_bin_t *bin = &shard->bins[pszind]; | ||
| 239 | bool do_batch_fill = false; | ||
| 240 | |||
| 241 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 242 | edata_t *edata = sec_shard_alloc_locked(tsdn, sec, shard, bin); | ||
| 243 | if (edata == NULL) { | ||
| 244 | if (!bin->being_batch_filled | ||
| 245 | && sec->opts.batch_fill_extra > 0) { | ||
| 246 | bin->being_batch_filled = true; | ||
| 247 | do_batch_fill = true; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 251 | if (edata == NULL) { | ||
| 252 | if (do_batch_fill) { | ||
| 253 | edata = sec_batch_fill_and_alloc(tsdn, sec, shard, bin, | ||
| 254 | size); | ||
| 255 | } else { | ||
| 256 | edata = pai_alloc(tsdn, sec->fallback, size, alignment, | ||
| 257 | zero, /* guarded */ false, frequent_reuse, | ||
| 258 | deferred_work_generated); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | return edata; | ||
| 262 | } | ||
| 263 | |||
| 264 | static bool | ||
| 265 | sec_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, | ||
| 266 | size_t new_size, bool zero, bool *deferred_work_generated) { | ||
| 267 | sec_t *sec = (sec_t *)self; | ||
| 268 | return pai_expand(tsdn, sec->fallback, edata, old_size, new_size, zero, | ||
| 269 | deferred_work_generated); | ||
| 270 | } | ||
| 271 | |||
| 272 | static bool | ||
| 273 | sec_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata, size_t old_size, | ||
| 274 | size_t new_size, bool *deferred_work_generated) { | ||
| 275 | sec_t *sec = (sec_t *)self; | ||
| 276 | return pai_shrink(tsdn, sec->fallback, edata, old_size, new_size, | ||
| 277 | deferred_work_generated); | ||
| 278 | } | ||
| 279 | |||
| 280 | static void | ||
| 281 | sec_flush_all_locked(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard) { | ||
| 282 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 283 | shard->bytes_cur = 0; | ||
| 284 | edata_list_active_t to_flush; | ||
| 285 | edata_list_active_init(&to_flush); | ||
| 286 | for (pszind_t i = 0; i < sec->npsizes; i++) { | ||
| 287 | sec_bin_t *bin = &shard->bins[i]; | ||
| 288 | bin->bytes_cur = 0; | ||
| 289 | edata_list_active_concat(&to_flush, &bin->freelist); | ||
| 290 | } | ||
| 291 | |||
| 292 | /* | ||
| 293 | * Ordinarily we would try to avoid doing the batch deallocation while | ||
| 294 | * holding the shard mutex, but the flush_all pathways only happen when | ||
| 295 | * we're disabling the HPA or resetting the arena, both of which are | ||
| 296 | * rare pathways. | ||
| 297 | */ | ||
| 298 | bool deferred_work_generated = false; | ||
| 299 | pai_dalloc_batch(tsdn, sec->fallback, &to_flush, | ||
| 300 | &deferred_work_generated); | ||
| 301 | } | ||
| 302 | |||
| 303 | static void | ||
| 304 | sec_shard_dalloc_and_unlock(tsdn_t *tsdn, sec_t *sec, sec_shard_t *shard, | ||
| 305 | edata_t *edata) { | ||
| 306 | malloc_mutex_assert_owner(tsdn, &shard->mtx); | ||
| 307 | assert(shard->bytes_cur <= sec->opts.max_bytes); | ||
| 308 | size_t size = edata_size_get(edata); | ||
| 309 | pszind_t pszind = sz_psz2ind(size); | ||
| 310 | assert(pszind < sec->npsizes); | ||
| 311 | /* | ||
| 312 | * Prepending here results in LIFO allocation per bin, which seems | ||
| 313 | * reasonable. | ||
| 314 | */ | ||
| 315 | sec_bin_t *bin = &shard->bins[pszind]; | ||
| 316 | edata_list_active_prepend(&bin->freelist, edata); | ||
| 317 | bin->bytes_cur += size; | ||
| 318 | shard->bytes_cur += size; | ||
| 319 | if (shard->bytes_cur > sec->opts.max_bytes) { | ||
| 320 | /* | ||
| 321 | * We've exceeded the shard limit. We make two nods in the | ||
| 322 | * direction of fragmentation avoidance: we flush everything in | ||
| 323 | * the shard, rather than one particular bin, and we hold the | ||
| 324 | * lock while flushing (in case one of the extents we flush is | ||
| 325 | * highly preferred from a fragmentation-avoidance perspective | ||
| 326 | * in the backing allocator). This has the extra advantage of | ||
| 327 | * not requiring advanced cache balancing strategies. | ||
| 328 | */ | ||
| 329 | sec_flush_some_and_unlock(tsdn, sec, shard); | ||
| 330 | malloc_mutex_assert_not_owner(tsdn, &shard->mtx); | ||
| 331 | } else { | ||
| 332 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 333 | } | ||
| 334 | } | ||
| 335 | |||
| 336 | static void | ||
| 337 | sec_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata, | ||
| 338 | bool *deferred_work_generated) { | ||
| 339 | sec_t *sec = (sec_t *)self; | ||
| 340 | if (sec->opts.nshards == 0 | ||
| 341 | || edata_size_get(edata) > sec->opts.max_alloc) { | ||
| 342 | pai_dalloc(tsdn, sec->fallback, edata, | ||
| 343 | deferred_work_generated); | ||
| 344 | return; | ||
| 345 | } | ||
| 346 | sec_shard_t *shard = sec_shard_pick(tsdn, sec); | ||
| 347 | malloc_mutex_lock(tsdn, &shard->mtx); | ||
| 348 | if (shard->enabled) { | ||
| 349 | sec_shard_dalloc_and_unlock(tsdn, sec, shard, edata); | ||
| 350 | } else { | ||
| 351 | malloc_mutex_unlock(tsdn, &shard->mtx); | ||
| 352 | pai_dalloc(tsdn, sec->fallback, edata, | ||
| 353 | deferred_work_generated); | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | void | ||
| 358 | sec_flush(tsdn_t *tsdn, sec_t *sec) { | ||
| 359 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 360 | malloc_mutex_lock(tsdn, &sec->shards[i].mtx); | ||
| 361 | sec_flush_all_locked(tsdn, sec, &sec->shards[i]); | ||
| 362 | malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | void | ||
| 367 | sec_disable(tsdn_t *tsdn, sec_t *sec) { | ||
| 368 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 369 | malloc_mutex_lock(tsdn, &sec->shards[i].mtx); | ||
| 370 | sec->shards[i].enabled = false; | ||
| 371 | sec_flush_all_locked(tsdn, sec, &sec->shards[i]); | ||
| 372 | malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | void | ||
| 377 | sec_stats_merge(tsdn_t *tsdn, sec_t *sec, sec_stats_t *stats) { | ||
| 378 | size_t sum = 0; | ||
| 379 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 380 | /* | ||
| 381 | * We could save these lock acquisitions by making bytes_cur | ||
| 382 | * atomic, but stats collection is rare anyways and we expect | ||
| 383 | * the number and type of stats to get more interesting. | ||
| 384 | */ | ||
| 385 | malloc_mutex_lock(tsdn, &sec->shards[i].mtx); | ||
| 386 | sum += sec->shards[i].bytes_cur; | ||
| 387 | malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); | ||
| 388 | } | ||
| 389 | stats->bytes += sum; | ||
| 390 | } | ||
| 391 | |||
| 392 | void | ||
| 393 | sec_mutex_stats_read(tsdn_t *tsdn, sec_t *sec, | ||
| 394 | mutex_prof_data_t *mutex_prof_data) { | ||
| 395 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 396 | malloc_mutex_lock(tsdn, &sec->shards[i].mtx); | ||
| 397 | malloc_mutex_prof_accum(tsdn, mutex_prof_data, | ||
| 398 | &sec->shards[i].mtx); | ||
| 399 | malloc_mutex_unlock(tsdn, &sec->shards[i].mtx); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | void | ||
| 404 | sec_prefork2(tsdn_t *tsdn, sec_t *sec) { | ||
| 405 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 406 | malloc_mutex_prefork(tsdn, &sec->shards[i].mtx); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | |||
| 410 | void | ||
| 411 | sec_postfork_parent(tsdn_t *tsdn, sec_t *sec) { | ||
| 412 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 413 | malloc_mutex_postfork_parent(tsdn, &sec->shards[i].mtx); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | |||
| 417 | void | ||
| 418 | sec_postfork_child(tsdn_t *tsdn, sec_t *sec) { | ||
| 419 | for (size_t i = 0; i < sec->opts.nshards; i++) { | ||
| 420 | malloc_mutex_postfork_child(tsdn, &sec->shards[i].mtx); | ||
| 421 | } | ||
| 422 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/stats.c b/examples/redis-unstable/deps/jemalloc/src/stats.c deleted file mode 100644 index efc70fd..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/stats.c +++ /dev/null | |||
| @@ -1,1973 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/ctl.h" | ||
| 6 | #include "jemalloc/internal/emitter.h" | ||
| 7 | #include "jemalloc/internal/fxp.h" | ||
| 8 | #include "jemalloc/internal/mutex.h" | ||
| 9 | #include "jemalloc/internal/mutex_prof.h" | ||
| 10 | #include "jemalloc/internal/prof_stats.h" | ||
| 11 | |||
| 12 | const char *global_mutex_names[mutex_prof_num_global_mutexes] = { | ||
| 13 | #define OP(mtx) #mtx, | ||
| 14 | MUTEX_PROF_GLOBAL_MUTEXES | ||
| 15 | #undef OP | ||
| 16 | }; | ||
| 17 | |||
| 18 | const char *arena_mutex_names[mutex_prof_num_arena_mutexes] = { | ||
| 19 | #define OP(mtx) #mtx, | ||
| 20 | MUTEX_PROF_ARENA_MUTEXES | ||
| 21 | #undef OP | ||
| 22 | }; | ||
| 23 | |||
| 24 | #define CTL_GET(n, v, t) do { \ | ||
| 25 | size_t sz = sizeof(t); \ | ||
| 26 | xmallctl(n, (void *)v, &sz, NULL, 0); \ | ||
| 27 | } while (0) | ||
| 28 | |||
| 29 | #define CTL_LEAF_PREPARE(mib, miblen, name) do { \ | ||
| 30 | assert(miblen < CTL_MAX_DEPTH); \ | ||
| 31 | size_t miblen_new = CTL_MAX_DEPTH; \ | ||
| 32 | xmallctlmibnametomib(mib, miblen, name, &miblen_new); \ | ||
| 33 | assert(miblen_new > miblen); \ | ||
| 34 | } while (0) | ||
| 35 | |||
| 36 | #define CTL_LEAF(mib, miblen, leaf, v, t) do { \ | ||
| 37 | assert(miblen < CTL_MAX_DEPTH); \ | ||
| 38 | size_t miblen_new = CTL_MAX_DEPTH; \ | ||
| 39 | size_t sz = sizeof(t); \ | ||
| 40 | xmallctlbymibname(mib, miblen, leaf, &miblen_new, (void *)v, \ | ||
| 41 | &sz, NULL, 0); \ | ||
| 42 | assert(miblen_new == miblen + 1); \ | ||
| 43 | } while (0) | ||
| 44 | |||
| 45 | #define CTL_M2_GET(n, i, v, t) do { \ | ||
| 46 | size_t mib[CTL_MAX_DEPTH]; \ | ||
| 47 | size_t miblen = sizeof(mib) / sizeof(size_t); \ | ||
| 48 | size_t sz = sizeof(t); \ | ||
| 49 | xmallctlnametomib(n, mib, &miblen); \ | ||
| 50 | mib[2] = (i); \ | ||
| 51 | xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0); \ | ||
| 52 | } while (0) | ||
| 53 | |||
| 54 | /******************************************************************************/ | ||
| 55 | /* Data. */ | ||
| 56 | |||
| 57 | bool opt_stats_print = false; | ||
| 58 | char opt_stats_print_opts[stats_print_tot_num_options+1] = ""; | ||
| 59 | |||
| 60 | int64_t opt_stats_interval = STATS_INTERVAL_DEFAULT; | ||
| 61 | char opt_stats_interval_opts[stats_print_tot_num_options+1] = ""; | ||
| 62 | |||
| 63 | static counter_accum_t stats_interval_accumulated; | ||
| 64 | /* Per thread batch accum size for stats_interval. */ | ||
| 65 | static uint64_t stats_interval_accum_batch; | ||
| 66 | |||
| 67 | /******************************************************************************/ | ||
| 68 | |||
| 69 | static uint64_t | ||
| 70 | rate_per_second(uint64_t value, uint64_t uptime_ns) { | ||
| 71 | uint64_t billion = 1000000000; | ||
| 72 | if (uptime_ns == 0 || value == 0) { | ||
| 73 | return 0; | ||
| 74 | } | ||
| 75 | if (uptime_ns < billion) { | ||
| 76 | return value; | ||
| 77 | } else { | ||
| 78 | uint64_t uptime_s = uptime_ns / billion; | ||
| 79 | return value / uptime_s; | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | /* Calculate x.yyy and output a string (takes a fixed sized char array). */ | ||
| 84 | static bool | ||
| 85 | get_rate_str(uint64_t dividend, uint64_t divisor, char str[6]) { | ||
| 86 | if (divisor == 0 || dividend > divisor) { | ||
| 87 | /* The rate is not supposed to be greater than 1. */ | ||
| 88 | return true; | ||
| 89 | } | ||
| 90 | if (dividend > 0) { | ||
| 91 | assert(UINT64_MAX / dividend >= 1000); | ||
| 92 | } | ||
| 93 | |||
| 94 | unsigned n = (unsigned)((dividend * 1000) / divisor); | ||
| 95 | if (n < 10) { | ||
| 96 | malloc_snprintf(str, 6, "0.00%u", n); | ||
| 97 | } else if (n < 100) { | ||
| 98 | malloc_snprintf(str, 6, "0.0%u", n); | ||
| 99 | } else if (n < 1000) { | ||
| 100 | malloc_snprintf(str, 6, "0.%u", n); | ||
| 101 | } else { | ||
| 102 | malloc_snprintf(str, 6, "1"); | ||
| 103 | } | ||
| 104 | |||
| 105 | return false; | ||
| 106 | } | ||
| 107 | |||
| 108 | static void | ||
| 109 | mutex_stats_init_cols(emitter_row_t *row, const char *table_name, | ||
| 110 | emitter_col_t *name, | ||
| 111 | emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], | ||
| 112 | emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) { | ||
| 113 | mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0; | ||
| 114 | mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0; | ||
| 115 | |||
| 116 | emitter_col_t *col; | ||
| 117 | |||
| 118 | if (name != NULL) { | ||
| 119 | emitter_col_init(name, row); | ||
| 120 | name->justify = emitter_justify_left; | ||
| 121 | name->width = 21; | ||
| 122 | name->type = emitter_type_title; | ||
| 123 | name->str_val = table_name; | ||
| 124 | } | ||
| 125 | |||
| 126 | #define WIDTH_uint32_t 12 | ||
| 127 | #define WIDTH_uint64_t 16 | ||
| 128 | #define OP(counter, counter_type, human, derived, base_counter) \ | ||
| 129 | col = &col_##counter_type[k_##counter_type]; \ | ||
| 130 | ++k_##counter_type; \ | ||
| 131 | emitter_col_init(col, row); \ | ||
| 132 | col->justify = emitter_justify_right; \ | ||
| 133 | col->width = derived ? 8 : WIDTH_##counter_type; \ | ||
| 134 | col->type = emitter_type_title; \ | ||
| 135 | col->str_val = human; | ||
| 136 | MUTEX_PROF_COUNTERS | ||
| 137 | #undef OP | ||
| 138 | #undef WIDTH_uint32_t | ||
| 139 | #undef WIDTH_uint64_t | ||
| 140 | col_uint64_t[mutex_counter_total_wait_time_ps].width = 10; | ||
| 141 | } | ||
| 142 | |||
| 143 | static void | ||
| 144 | mutex_stats_read_global(size_t mib[], size_t miblen, const char *name, | ||
| 145 | emitter_col_t *col_name, | ||
| 146 | emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], | ||
| 147 | emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], | ||
| 148 | uint64_t uptime) { | ||
| 149 | CTL_LEAF_PREPARE(mib, miblen, name); | ||
| 150 | size_t miblen_name = miblen + 1; | ||
| 151 | |||
| 152 | col_name->str_val = name; | ||
| 153 | |||
| 154 | emitter_col_t *dst; | ||
| 155 | #define EMITTER_TYPE_uint32_t emitter_type_uint32 | ||
| 156 | #define EMITTER_TYPE_uint64_t emitter_type_uint64 | ||
| 157 | #define OP(counter, counter_type, human, derived, base_counter) \ | ||
| 158 | dst = &col_##counter_type[mutex_counter_##counter]; \ | ||
| 159 | dst->type = EMITTER_TYPE_##counter_type; \ | ||
| 160 | if (!derived) { \ | ||
| 161 | CTL_LEAF(mib, miblen_name, #counter, \ | ||
| 162 | (counter_type *)&dst->bool_val, counter_type); \ | ||
| 163 | } else { \ | ||
| 164 | emitter_col_t *base = \ | ||
| 165 | &col_##counter_type[mutex_counter_##base_counter]; \ | ||
| 166 | dst->counter_type##_val = \ | ||
| 167 | (counter_type)rate_per_second( \ | ||
| 168 | base->counter_type##_val, uptime); \ | ||
| 169 | } | ||
| 170 | MUTEX_PROF_COUNTERS | ||
| 171 | #undef OP | ||
| 172 | #undef EMITTER_TYPE_uint32_t | ||
| 173 | #undef EMITTER_TYPE_uint64_t | ||
| 174 | } | ||
| 175 | |||
| 176 | static void | ||
| 177 | mutex_stats_read_arena(size_t mib[], size_t miblen, const char *name, | ||
| 178 | emitter_col_t *col_name, | ||
| 179 | emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], | ||
| 180 | emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], | ||
| 181 | uint64_t uptime) { | ||
| 182 | CTL_LEAF_PREPARE(mib, miblen, name); | ||
| 183 | size_t miblen_name = miblen + 1; | ||
| 184 | |||
| 185 | col_name->str_val = name; | ||
| 186 | |||
| 187 | emitter_col_t *dst; | ||
| 188 | #define EMITTER_TYPE_uint32_t emitter_type_uint32 | ||
| 189 | #define EMITTER_TYPE_uint64_t emitter_type_uint64 | ||
| 190 | #define OP(counter, counter_type, human, derived, base_counter) \ | ||
| 191 | dst = &col_##counter_type[mutex_counter_##counter]; \ | ||
| 192 | dst->type = EMITTER_TYPE_##counter_type; \ | ||
| 193 | if (!derived) { \ | ||
| 194 | CTL_LEAF(mib, miblen_name, #counter, \ | ||
| 195 | (counter_type *)&dst->bool_val, counter_type); \ | ||
| 196 | } else { \ | ||
| 197 | emitter_col_t *base = \ | ||
| 198 | &col_##counter_type[mutex_counter_##base_counter]; \ | ||
| 199 | dst->counter_type##_val = \ | ||
| 200 | (counter_type)rate_per_second( \ | ||
| 201 | base->counter_type##_val, uptime); \ | ||
| 202 | } | ||
| 203 | MUTEX_PROF_COUNTERS | ||
| 204 | #undef OP | ||
| 205 | #undef EMITTER_TYPE_uint32_t | ||
| 206 | #undef EMITTER_TYPE_uint64_t | ||
| 207 | } | ||
| 208 | |||
| 209 | static void | ||
| 210 | mutex_stats_read_arena_bin(size_t mib[], size_t miblen, | ||
| 211 | emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], | ||
| 212 | emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters], | ||
| 213 | uint64_t uptime) { | ||
| 214 | CTL_LEAF_PREPARE(mib, miblen, "mutex"); | ||
| 215 | size_t miblen_mutex = miblen + 1; | ||
| 216 | |||
| 217 | emitter_col_t *dst; | ||
| 218 | |||
| 219 | #define EMITTER_TYPE_uint32_t emitter_type_uint32 | ||
| 220 | #define EMITTER_TYPE_uint64_t emitter_type_uint64 | ||
| 221 | #define OP(counter, counter_type, human, derived, base_counter) \ | ||
| 222 | dst = &col_##counter_type[mutex_counter_##counter]; \ | ||
| 223 | dst->type = EMITTER_TYPE_##counter_type; \ | ||
| 224 | if (!derived) { \ | ||
| 225 | CTL_LEAF(mib, miblen_mutex, #counter, \ | ||
| 226 | (counter_type *)&dst->bool_val, counter_type); \ | ||
| 227 | } else { \ | ||
| 228 | emitter_col_t *base = \ | ||
| 229 | &col_##counter_type[mutex_counter_##base_counter]; \ | ||
| 230 | dst->counter_type##_val = \ | ||
| 231 | (counter_type)rate_per_second( \ | ||
| 232 | base->counter_type##_val, uptime); \ | ||
| 233 | } | ||
| 234 | MUTEX_PROF_COUNTERS | ||
| 235 | #undef OP | ||
| 236 | #undef EMITTER_TYPE_uint32_t | ||
| 237 | #undef EMITTER_TYPE_uint64_t | ||
| 238 | } | ||
| 239 | |||
| 240 | /* "row" can be NULL to avoid emitting in table mode. */ | ||
| 241 | static void | ||
| 242 | mutex_stats_emit(emitter_t *emitter, emitter_row_t *row, | ||
| 243 | emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters], | ||
| 244 | emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) { | ||
| 245 | if (row != NULL) { | ||
| 246 | emitter_table_row(emitter, row); | ||
| 247 | } | ||
| 248 | |||
| 249 | mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0; | ||
| 250 | mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0; | ||
| 251 | |||
| 252 | emitter_col_t *col; | ||
| 253 | |||
| 254 | #define EMITTER_TYPE_uint32_t emitter_type_uint32 | ||
| 255 | #define EMITTER_TYPE_uint64_t emitter_type_uint64 | ||
| 256 | #define OP(counter, type, human, derived, base_counter) \ | ||
| 257 | if (!derived) { \ | ||
| 258 | col = &col_##type[k_##type]; \ | ||
| 259 | ++k_##type; \ | ||
| 260 | emitter_json_kv(emitter, #counter, EMITTER_TYPE_##type, \ | ||
| 261 | (const void *)&col->bool_val); \ | ||
| 262 | } | ||
| 263 | MUTEX_PROF_COUNTERS; | ||
| 264 | #undef OP | ||
| 265 | #undef EMITTER_TYPE_uint32_t | ||
| 266 | #undef EMITTER_TYPE_uint64_t | ||
| 267 | } | ||
| 268 | |||
| 269 | #define COL_DECLARE(column_name) \ | ||
| 270 | emitter_col_t col_##column_name; | ||
| 271 | |||
| 272 | #define COL_INIT(row_name, column_name, left_or_right, col_width, etype)\ | ||
| 273 | emitter_col_init(&col_##column_name, &row_name); \ | ||
| 274 | col_##column_name.justify = emitter_justify_##left_or_right; \ | ||
| 275 | col_##column_name.width = col_width; \ | ||
| 276 | col_##column_name.type = emitter_type_##etype; | ||
| 277 | |||
| 278 | #define COL(row_name, column_name, left_or_right, col_width, etype) \ | ||
| 279 | COL_DECLARE(column_name); \ | ||
| 280 | COL_INIT(row_name, column_name, left_or_right, col_width, etype) | ||
| 281 | |||
| 282 | #define COL_HDR_DECLARE(column_name) \ | ||
| 283 | COL_DECLARE(column_name); \ | ||
| 284 | emitter_col_t header_##column_name; | ||
| 285 | |||
| 286 | #define COL_HDR_INIT(row_name, column_name, human, left_or_right, \ | ||
| 287 | col_width, etype) \ | ||
| 288 | COL_INIT(row_name, column_name, left_or_right, col_width, etype)\ | ||
| 289 | emitter_col_init(&header_##column_name, &header_##row_name); \ | ||
| 290 | header_##column_name.justify = emitter_justify_##left_or_right; \ | ||
| 291 | header_##column_name.width = col_width; \ | ||
| 292 | header_##column_name.type = emitter_type_title; \ | ||
| 293 | header_##column_name.str_val = human ? human : #column_name; | ||
| 294 | |||
| 295 | #define COL_HDR(row_name, column_name, human, left_or_right, col_width, \ | ||
| 296 | etype) \ | ||
| 297 | COL_HDR_DECLARE(column_name) \ | ||
| 298 | COL_HDR_INIT(row_name, column_name, human, left_or_right, \ | ||
| 299 | col_width, etype) | ||
| 300 | |||
| 301 | JEMALLOC_COLD | ||
| 302 | static void | ||
| 303 | stats_arena_bins_print(emitter_t *emitter, bool mutex, unsigned i, | ||
| 304 | uint64_t uptime) { | ||
| 305 | size_t page; | ||
| 306 | bool in_gap, in_gap_prev; | ||
| 307 | unsigned nbins, j; | ||
| 308 | |||
| 309 | CTL_GET("arenas.page", &page, size_t); | ||
| 310 | |||
| 311 | CTL_GET("arenas.nbins", &nbins, unsigned); | ||
| 312 | |||
| 313 | emitter_row_t header_row; | ||
| 314 | emitter_row_init(&header_row); | ||
| 315 | |||
| 316 | emitter_row_t row; | ||
| 317 | emitter_row_init(&row); | ||
| 318 | |||
| 319 | bool prof_stats_on = config_prof && opt_prof && opt_prof_stats | ||
| 320 | && i == MALLCTL_ARENAS_ALL; | ||
| 321 | |||
| 322 | COL_HDR(row, size, NULL, right, 20, size) | ||
| 323 | COL_HDR(row, ind, NULL, right, 4, unsigned) | ||
| 324 | COL_HDR(row, allocated, NULL, right, 13, uint64) | ||
| 325 | COL_HDR(row, nmalloc, NULL, right, 13, uint64) | ||
| 326 | COL_HDR(row, nmalloc_ps, "(#/sec)", right, 8, uint64) | ||
| 327 | COL_HDR(row, ndalloc, NULL, right, 13, uint64) | ||
| 328 | COL_HDR(row, ndalloc_ps, "(#/sec)", right, 8, uint64) | ||
| 329 | COL_HDR(row, nrequests, NULL, right, 13, uint64) | ||
| 330 | COL_HDR(row, nrequests_ps, "(#/sec)", right, 10, uint64) | ||
| 331 | COL_HDR_DECLARE(prof_live_requested); | ||
| 332 | COL_HDR_DECLARE(prof_live_count); | ||
| 333 | COL_HDR_DECLARE(prof_accum_requested); | ||
| 334 | COL_HDR_DECLARE(prof_accum_count); | ||
| 335 | if (prof_stats_on) { | ||
| 336 | COL_HDR_INIT(row, prof_live_requested, NULL, right, 21, uint64) | ||
| 337 | COL_HDR_INIT(row, prof_live_count, NULL, right, 17, uint64) | ||
| 338 | COL_HDR_INIT(row, prof_accum_requested, NULL, right, 21, uint64) | ||
| 339 | COL_HDR_INIT(row, prof_accum_count, NULL, right, 17, uint64) | ||
| 340 | } | ||
| 341 | COL_HDR(row, nshards, NULL, right, 9, unsigned) | ||
| 342 | COL_HDR(row, curregs, NULL, right, 13, size) | ||
| 343 | COL_HDR(row, curslabs, NULL, right, 13, size) | ||
| 344 | COL_HDR(row, nonfull_slabs, NULL, right, 15, size) | ||
| 345 | COL_HDR(row, regs, NULL, right, 5, unsigned) | ||
| 346 | COL_HDR(row, pgs, NULL, right, 4, size) | ||
| 347 | /* To buffer a right- and left-justified column. */ | ||
| 348 | COL_HDR(row, justify_spacer, NULL, right, 1, title) | ||
| 349 | COL_HDR(row, util, NULL, right, 6, title) | ||
| 350 | COL_HDR(row, nfills, NULL, right, 13, uint64) | ||
| 351 | COL_HDR(row, nfills_ps, "(#/sec)", right, 8, uint64) | ||
| 352 | COL_HDR(row, nflushes, NULL, right, 13, uint64) | ||
| 353 | COL_HDR(row, nflushes_ps, "(#/sec)", right, 8, uint64) | ||
| 354 | COL_HDR(row, nslabs, NULL, right, 13, uint64) | ||
| 355 | COL_HDR(row, nreslabs, NULL, right, 13, uint64) | ||
| 356 | COL_HDR(row, nreslabs_ps, "(#/sec)", right, 8, uint64) | ||
| 357 | |||
| 358 | /* Don't want to actually print the name. */ | ||
| 359 | header_justify_spacer.str_val = " "; | ||
| 360 | col_justify_spacer.str_val = " "; | ||
| 361 | |||
| 362 | emitter_col_t col_mutex64[mutex_prof_num_uint64_t_counters]; | ||
| 363 | emitter_col_t col_mutex32[mutex_prof_num_uint32_t_counters]; | ||
| 364 | |||
| 365 | emitter_col_t header_mutex64[mutex_prof_num_uint64_t_counters]; | ||
| 366 | emitter_col_t header_mutex32[mutex_prof_num_uint32_t_counters]; | ||
| 367 | |||
| 368 | if (mutex) { | ||
| 369 | mutex_stats_init_cols(&row, NULL, NULL, col_mutex64, | ||
| 370 | col_mutex32); | ||
| 371 | mutex_stats_init_cols(&header_row, NULL, NULL, header_mutex64, | ||
| 372 | header_mutex32); | ||
| 373 | } | ||
| 374 | |||
| 375 | /* | ||
| 376 | * We print a "bins:" header as part of the table row; we need to adjust | ||
| 377 | * the header size column to compensate. | ||
| 378 | */ | ||
| 379 | header_size.width -=5; | ||
| 380 | emitter_table_printf(emitter, "bins:"); | ||
| 381 | emitter_table_row(emitter, &header_row); | ||
| 382 | emitter_json_array_kv_begin(emitter, "bins"); | ||
| 383 | |||
| 384 | size_t stats_arenas_mib[CTL_MAX_DEPTH]; | ||
| 385 | CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); | ||
| 386 | stats_arenas_mib[2] = i; | ||
| 387 | CTL_LEAF_PREPARE(stats_arenas_mib, 3, "bins"); | ||
| 388 | |||
| 389 | size_t arenas_bin_mib[CTL_MAX_DEPTH]; | ||
| 390 | CTL_LEAF_PREPARE(arenas_bin_mib, 0, "arenas.bin"); | ||
| 391 | |||
| 392 | size_t prof_stats_mib[CTL_MAX_DEPTH]; | ||
| 393 | if (prof_stats_on) { | ||
| 394 | CTL_LEAF_PREPARE(prof_stats_mib, 0, "prof.stats.bins"); | ||
| 395 | } | ||
| 396 | |||
| 397 | for (j = 0, in_gap = false; j < nbins; j++) { | ||
| 398 | uint64_t nslabs; | ||
| 399 | size_t reg_size, slab_size, curregs; | ||
| 400 | size_t curslabs; | ||
| 401 | size_t nonfull_slabs; | ||
| 402 | uint32_t nregs, nshards; | ||
| 403 | uint64_t nmalloc, ndalloc, nrequests, nfills, nflushes; | ||
| 404 | uint64_t nreslabs; | ||
| 405 | prof_stats_t prof_live; | ||
| 406 | prof_stats_t prof_accum; | ||
| 407 | |||
| 408 | stats_arenas_mib[4] = j; | ||
| 409 | arenas_bin_mib[2] = j; | ||
| 410 | |||
| 411 | CTL_LEAF(stats_arenas_mib, 5, "nslabs", &nslabs, uint64_t); | ||
| 412 | |||
| 413 | if (prof_stats_on) { | ||
| 414 | prof_stats_mib[3] = j; | ||
| 415 | CTL_LEAF(prof_stats_mib, 4, "live", &prof_live, | ||
| 416 | prof_stats_t); | ||
| 417 | CTL_LEAF(prof_stats_mib, 4, "accum", &prof_accum, | ||
| 418 | prof_stats_t); | ||
| 419 | } | ||
| 420 | |||
| 421 | in_gap_prev = in_gap; | ||
| 422 | if (prof_stats_on) { | ||
| 423 | in_gap = (nslabs == 0 && prof_accum.count == 0); | ||
| 424 | } else { | ||
| 425 | in_gap = (nslabs == 0); | ||
| 426 | } | ||
| 427 | |||
| 428 | if (in_gap_prev && !in_gap) { | ||
| 429 | emitter_table_printf(emitter, | ||
| 430 | " ---\n"); | ||
| 431 | } | ||
| 432 | |||
| 433 | if (in_gap && !emitter_outputs_json(emitter)) { | ||
| 434 | continue; | ||
| 435 | } | ||
| 436 | |||
| 437 | CTL_LEAF(arenas_bin_mib, 3, "size", ®_size, size_t); | ||
| 438 | CTL_LEAF(arenas_bin_mib, 3, "nregs", &nregs, uint32_t); | ||
| 439 | CTL_LEAF(arenas_bin_mib, 3, "slab_size", &slab_size, size_t); | ||
| 440 | CTL_LEAF(arenas_bin_mib, 3, "nshards", &nshards, uint32_t); | ||
| 441 | CTL_LEAF(stats_arenas_mib, 5, "nmalloc", &nmalloc, uint64_t); | ||
| 442 | CTL_LEAF(stats_arenas_mib, 5, "ndalloc", &ndalloc, uint64_t); | ||
| 443 | CTL_LEAF(stats_arenas_mib, 5, "curregs", &curregs, size_t); | ||
| 444 | CTL_LEAF(stats_arenas_mib, 5, "nrequests", &nrequests, | ||
| 445 | uint64_t); | ||
| 446 | CTL_LEAF(stats_arenas_mib, 5, "nfills", &nfills, uint64_t); | ||
| 447 | CTL_LEAF(stats_arenas_mib, 5, "nflushes", &nflushes, uint64_t); | ||
| 448 | CTL_LEAF(stats_arenas_mib, 5, "nreslabs", &nreslabs, uint64_t); | ||
| 449 | CTL_LEAF(stats_arenas_mib, 5, "curslabs", &curslabs, size_t); | ||
| 450 | CTL_LEAF(stats_arenas_mib, 5, "nonfull_slabs", &nonfull_slabs, | ||
| 451 | size_t); | ||
| 452 | |||
| 453 | if (mutex) { | ||
| 454 | mutex_stats_read_arena_bin(stats_arenas_mib, 5, | ||
| 455 | col_mutex64, col_mutex32, uptime); | ||
| 456 | } | ||
| 457 | |||
| 458 | emitter_json_object_begin(emitter); | ||
| 459 | emitter_json_kv(emitter, "nmalloc", emitter_type_uint64, | ||
| 460 | &nmalloc); | ||
| 461 | emitter_json_kv(emitter, "ndalloc", emitter_type_uint64, | ||
| 462 | &ndalloc); | ||
| 463 | emitter_json_kv(emitter, "curregs", emitter_type_size, | ||
| 464 | &curregs); | ||
| 465 | emitter_json_kv(emitter, "nrequests", emitter_type_uint64, | ||
| 466 | &nrequests); | ||
| 467 | if (prof_stats_on) { | ||
| 468 | emitter_json_kv(emitter, "prof_live_requested", | ||
| 469 | emitter_type_uint64, &prof_live.req_sum); | ||
| 470 | emitter_json_kv(emitter, "prof_live_count", | ||
| 471 | emitter_type_uint64, &prof_live.count); | ||
| 472 | emitter_json_kv(emitter, "prof_accum_requested", | ||
| 473 | emitter_type_uint64, &prof_accum.req_sum); | ||
| 474 | emitter_json_kv(emitter, "prof_accum_count", | ||
| 475 | emitter_type_uint64, &prof_accum.count); | ||
| 476 | } | ||
| 477 | emitter_json_kv(emitter, "nfills", emitter_type_uint64, | ||
| 478 | &nfills); | ||
| 479 | emitter_json_kv(emitter, "nflushes", emitter_type_uint64, | ||
| 480 | &nflushes); | ||
| 481 | emitter_json_kv(emitter, "nreslabs", emitter_type_uint64, | ||
| 482 | &nreslabs); | ||
| 483 | emitter_json_kv(emitter, "curslabs", emitter_type_size, | ||
| 484 | &curslabs); | ||
| 485 | emitter_json_kv(emitter, "nonfull_slabs", emitter_type_size, | ||
| 486 | &nonfull_slabs); | ||
| 487 | if (mutex) { | ||
| 488 | emitter_json_object_kv_begin(emitter, "mutex"); | ||
| 489 | mutex_stats_emit(emitter, NULL, col_mutex64, | ||
| 490 | col_mutex32); | ||
| 491 | emitter_json_object_end(emitter); | ||
| 492 | } | ||
| 493 | emitter_json_object_end(emitter); | ||
| 494 | |||
| 495 | size_t availregs = nregs * curslabs; | ||
| 496 | char util[6]; | ||
| 497 | if (get_rate_str((uint64_t)curregs, (uint64_t)availregs, util)) | ||
| 498 | { | ||
| 499 | if (availregs == 0) { | ||
| 500 | malloc_snprintf(util, sizeof(util), "1"); | ||
| 501 | } else if (curregs > availregs) { | ||
| 502 | /* | ||
| 503 | * Race detected: the counters were read in | ||
| 504 | * separate mallctl calls and concurrent | ||
| 505 | * operations happened in between. In this case | ||
| 506 | * no meaningful utilization can be computed. | ||
| 507 | */ | ||
| 508 | malloc_snprintf(util, sizeof(util), " race"); | ||
| 509 | } else { | ||
| 510 | not_reached(); | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | col_size.size_val = reg_size; | ||
| 515 | col_ind.unsigned_val = j; | ||
| 516 | col_allocated.size_val = curregs * reg_size; | ||
| 517 | col_nmalloc.uint64_val = nmalloc; | ||
| 518 | col_nmalloc_ps.uint64_val = rate_per_second(nmalloc, uptime); | ||
| 519 | col_ndalloc.uint64_val = ndalloc; | ||
| 520 | col_ndalloc_ps.uint64_val = rate_per_second(ndalloc, uptime); | ||
| 521 | col_nrequests.uint64_val = nrequests; | ||
| 522 | col_nrequests_ps.uint64_val = rate_per_second(nrequests, uptime); | ||
| 523 | if (prof_stats_on) { | ||
| 524 | col_prof_live_requested.uint64_val = prof_live.req_sum; | ||
| 525 | col_prof_live_count.uint64_val = prof_live.count; | ||
| 526 | col_prof_accum_requested.uint64_val = | ||
| 527 | prof_accum.req_sum; | ||
| 528 | col_prof_accum_count.uint64_val = prof_accum.count; | ||
| 529 | } | ||
| 530 | col_nshards.unsigned_val = nshards; | ||
| 531 | col_curregs.size_val = curregs; | ||
| 532 | col_curslabs.size_val = curslabs; | ||
| 533 | col_nonfull_slabs.size_val = nonfull_slabs; | ||
| 534 | col_regs.unsigned_val = nregs; | ||
| 535 | col_pgs.size_val = slab_size / page; | ||
| 536 | col_util.str_val = util; | ||
| 537 | col_nfills.uint64_val = nfills; | ||
| 538 | col_nfills_ps.uint64_val = rate_per_second(nfills, uptime); | ||
| 539 | col_nflushes.uint64_val = nflushes; | ||
| 540 | col_nflushes_ps.uint64_val = rate_per_second(nflushes, uptime); | ||
| 541 | col_nslabs.uint64_val = nslabs; | ||
| 542 | col_nreslabs.uint64_val = nreslabs; | ||
| 543 | col_nreslabs_ps.uint64_val = rate_per_second(nreslabs, uptime); | ||
| 544 | |||
| 545 | /* | ||
| 546 | * Note that mutex columns were initialized above, if mutex == | ||
| 547 | * true. | ||
| 548 | */ | ||
| 549 | |||
| 550 | emitter_table_row(emitter, &row); | ||
| 551 | } | ||
| 552 | emitter_json_array_end(emitter); /* Close "bins". */ | ||
| 553 | |||
| 554 | if (in_gap) { | ||
| 555 | emitter_table_printf(emitter, " ---\n"); | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | JEMALLOC_COLD | ||
| 560 | static void | ||
| 561 | stats_arena_lextents_print(emitter_t *emitter, unsigned i, uint64_t uptime) { | ||
| 562 | unsigned nbins, nlextents, j; | ||
| 563 | bool in_gap, in_gap_prev; | ||
| 564 | |||
| 565 | CTL_GET("arenas.nbins", &nbins, unsigned); | ||
| 566 | CTL_GET("arenas.nlextents", &nlextents, unsigned); | ||
| 567 | |||
| 568 | emitter_row_t header_row; | ||
| 569 | emitter_row_init(&header_row); | ||
| 570 | emitter_row_t row; | ||
| 571 | emitter_row_init(&row); | ||
| 572 | |||
| 573 | bool prof_stats_on = config_prof && opt_prof && opt_prof_stats | ||
| 574 | && i == MALLCTL_ARENAS_ALL; | ||
| 575 | |||
| 576 | COL_HDR(row, size, NULL, right, 20, size) | ||
| 577 | COL_HDR(row, ind, NULL, right, 4, unsigned) | ||
| 578 | COL_HDR(row, allocated, NULL, right, 13, size) | ||
| 579 | COL_HDR(row, nmalloc, NULL, right, 13, uint64) | ||
| 580 | COL_HDR(row, nmalloc_ps, "(#/sec)", right, 8, uint64) | ||
| 581 | COL_HDR(row, ndalloc, NULL, right, 13, uint64) | ||
| 582 | COL_HDR(row, ndalloc_ps, "(#/sec)", right, 8, uint64) | ||
| 583 | COL_HDR(row, nrequests, NULL, right, 13, uint64) | ||
| 584 | COL_HDR(row, nrequests_ps, "(#/sec)", right, 8, uint64) | ||
| 585 | COL_HDR_DECLARE(prof_live_requested) | ||
| 586 | COL_HDR_DECLARE(prof_live_count) | ||
| 587 | COL_HDR_DECLARE(prof_accum_requested) | ||
| 588 | COL_HDR_DECLARE(prof_accum_count) | ||
| 589 | if (prof_stats_on) { | ||
| 590 | COL_HDR_INIT(row, prof_live_requested, NULL, right, 21, uint64) | ||
| 591 | COL_HDR_INIT(row, prof_live_count, NULL, right, 17, uint64) | ||
| 592 | COL_HDR_INIT(row, prof_accum_requested, NULL, right, 21, uint64) | ||
| 593 | COL_HDR_INIT(row, prof_accum_count, NULL, right, 17, uint64) | ||
| 594 | } | ||
| 595 | COL_HDR(row, curlextents, NULL, right, 13, size) | ||
| 596 | |||
| 597 | /* As with bins, we label the large extents table. */ | ||
| 598 | header_size.width -= 6; | ||
| 599 | emitter_table_printf(emitter, "large:"); | ||
| 600 | emitter_table_row(emitter, &header_row); | ||
| 601 | emitter_json_array_kv_begin(emitter, "lextents"); | ||
| 602 | |||
| 603 | size_t stats_arenas_mib[CTL_MAX_DEPTH]; | ||
| 604 | CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); | ||
| 605 | stats_arenas_mib[2] = i; | ||
| 606 | CTL_LEAF_PREPARE(stats_arenas_mib, 3, "lextents"); | ||
| 607 | |||
| 608 | size_t arenas_lextent_mib[CTL_MAX_DEPTH]; | ||
| 609 | CTL_LEAF_PREPARE(arenas_lextent_mib, 0, "arenas.lextent"); | ||
| 610 | |||
| 611 | size_t prof_stats_mib[CTL_MAX_DEPTH]; | ||
| 612 | if (prof_stats_on) { | ||
| 613 | CTL_LEAF_PREPARE(prof_stats_mib, 0, "prof.stats.lextents"); | ||
| 614 | } | ||
| 615 | |||
| 616 | for (j = 0, in_gap = false; j < nlextents; j++) { | ||
| 617 | uint64_t nmalloc, ndalloc, nrequests; | ||
| 618 | size_t lextent_size, curlextents; | ||
| 619 | prof_stats_t prof_live; | ||
| 620 | prof_stats_t prof_accum; | ||
| 621 | |||
| 622 | stats_arenas_mib[4] = j; | ||
| 623 | arenas_lextent_mib[2] = j; | ||
| 624 | |||
| 625 | CTL_LEAF(stats_arenas_mib, 5, "nmalloc", &nmalloc, uint64_t); | ||
| 626 | CTL_LEAF(stats_arenas_mib, 5, "ndalloc", &ndalloc, uint64_t); | ||
| 627 | CTL_LEAF(stats_arenas_mib, 5, "nrequests", &nrequests, | ||
| 628 | uint64_t); | ||
| 629 | |||
| 630 | in_gap_prev = in_gap; | ||
| 631 | in_gap = (nrequests == 0); | ||
| 632 | |||
| 633 | if (in_gap_prev && !in_gap) { | ||
| 634 | emitter_table_printf(emitter, | ||
| 635 | " ---\n"); | ||
| 636 | } | ||
| 637 | |||
| 638 | CTL_LEAF(arenas_lextent_mib, 3, "size", &lextent_size, size_t); | ||
| 639 | CTL_LEAF(stats_arenas_mib, 5, "curlextents", &curlextents, | ||
| 640 | size_t); | ||
| 641 | |||
| 642 | if (prof_stats_on) { | ||
| 643 | prof_stats_mib[3] = j; | ||
| 644 | CTL_LEAF(prof_stats_mib, 4, "live", &prof_live, | ||
| 645 | prof_stats_t); | ||
| 646 | CTL_LEAF(prof_stats_mib, 4, "accum", &prof_accum, | ||
| 647 | prof_stats_t); | ||
| 648 | } | ||
| 649 | |||
| 650 | emitter_json_object_begin(emitter); | ||
| 651 | if (prof_stats_on) { | ||
| 652 | emitter_json_kv(emitter, "prof_live_requested", | ||
| 653 | emitter_type_uint64, &prof_live.req_sum); | ||
| 654 | emitter_json_kv(emitter, "prof_live_count", | ||
| 655 | emitter_type_uint64, &prof_live.count); | ||
| 656 | emitter_json_kv(emitter, "prof_accum_requested", | ||
| 657 | emitter_type_uint64, &prof_accum.req_sum); | ||
| 658 | emitter_json_kv(emitter, "prof_accum_count", | ||
| 659 | emitter_type_uint64, &prof_accum.count); | ||
| 660 | } | ||
| 661 | emitter_json_kv(emitter, "curlextents", emitter_type_size, | ||
| 662 | &curlextents); | ||
| 663 | emitter_json_object_end(emitter); | ||
| 664 | |||
| 665 | col_size.size_val = lextent_size; | ||
| 666 | col_ind.unsigned_val = nbins + j; | ||
| 667 | col_allocated.size_val = curlextents * lextent_size; | ||
| 668 | col_nmalloc.uint64_val = nmalloc; | ||
| 669 | col_nmalloc_ps.uint64_val = rate_per_second(nmalloc, uptime); | ||
| 670 | col_ndalloc.uint64_val = ndalloc; | ||
| 671 | col_ndalloc_ps.uint64_val = rate_per_second(ndalloc, uptime); | ||
| 672 | col_nrequests.uint64_val = nrequests; | ||
| 673 | col_nrequests_ps.uint64_val = rate_per_second(nrequests, uptime); | ||
| 674 | if (prof_stats_on) { | ||
| 675 | col_prof_live_requested.uint64_val = prof_live.req_sum; | ||
| 676 | col_prof_live_count.uint64_val = prof_live.count; | ||
| 677 | col_prof_accum_requested.uint64_val = | ||
| 678 | prof_accum.req_sum; | ||
| 679 | col_prof_accum_count.uint64_val = prof_accum.count; | ||
| 680 | } | ||
| 681 | col_curlextents.size_val = curlextents; | ||
| 682 | |||
| 683 | if (!in_gap) { | ||
| 684 | emitter_table_row(emitter, &row); | ||
| 685 | } | ||
| 686 | } | ||
| 687 | emitter_json_array_end(emitter); /* Close "lextents". */ | ||
| 688 | if (in_gap) { | ||
| 689 | emitter_table_printf(emitter, " ---\n"); | ||
| 690 | } | ||
| 691 | } | ||
| 692 | |||
| 693 | JEMALLOC_COLD | ||
| 694 | static void | ||
| 695 | stats_arena_extents_print(emitter_t *emitter, unsigned i) { | ||
| 696 | unsigned j; | ||
| 697 | bool in_gap, in_gap_prev; | ||
| 698 | emitter_row_t header_row; | ||
| 699 | emitter_row_init(&header_row); | ||
| 700 | emitter_row_t row; | ||
| 701 | emitter_row_init(&row); | ||
| 702 | |||
| 703 | COL_HDR(row, size, NULL, right, 20, size) | ||
| 704 | COL_HDR(row, ind, NULL, right, 4, unsigned) | ||
| 705 | COL_HDR(row, ndirty, NULL, right, 13, size) | ||
| 706 | COL_HDR(row, dirty, NULL, right, 13, size) | ||
| 707 | COL_HDR(row, nmuzzy, NULL, right, 13, size) | ||
| 708 | COL_HDR(row, muzzy, NULL, right, 13, size) | ||
| 709 | COL_HDR(row, nretained, NULL, right, 13, size) | ||
| 710 | COL_HDR(row, retained, NULL, right, 13, size) | ||
| 711 | COL_HDR(row, ntotal, NULL, right, 13, size) | ||
| 712 | COL_HDR(row, total, NULL, right, 13, size) | ||
| 713 | |||
| 714 | /* Label this section. */ | ||
| 715 | header_size.width -= 8; | ||
| 716 | emitter_table_printf(emitter, "extents:"); | ||
| 717 | emitter_table_row(emitter, &header_row); | ||
| 718 | emitter_json_array_kv_begin(emitter, "extents"); | ||
| 719 | |||
| 720 | size_t stats_arenas_mib[CTL_MAX_DEPTH]; | ||
| 721 | CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); | ||
| 722 | stats_arenas_mib[2] = i; | ||
| 723 | CTL_LEAF_PREPARE(stats_arenas_mib, 3, "extents"); | ||
| 724 | |||
| 725 | in_gap = false; | ||
| 726 | for (j = 0; j < SC_NPSIZES; j++) { | ||
| 727 | size_t ndirty, nmuzzy, nretained, total, dirty_bytes, | ||
| 728 | muzzy_bytes, retained_bytes, total_bytes; | ||
| 729 | stats_arenas_mib[4] = j; | ||
| 730 | |||
| 731 | CTL_LEAF(stats_arenas_mib, 5, "ndirty", &ndirty, size_t); | ||
| 732 | CTL_LEAF(stats_arenas_mib, 5, "nmuzzy", &nmuzzy, size_t); | ||
| 733 | CTL_LEAF(stats_arenas_mib, 5, "nretained", &nretained, size_t); | ||
| 734 | CTL_LEAF(stats_arenas_mib, 5, "dirty_bytes", &dirty_bytes, | ||
| 735 | size_t); | ||
| 736 | CTL_LEAF(stats_arenas_mib, 5, "muzzy_bytes", &muzzy_bytes, | ||
| 737 | size_t); | ||
| 738 | CTL_LEAF(stats_arenas_mib, 5, "retained_bytes", | ||
| 739 | &retained_bytes, size_t); | ||
| 740 | |||
| 741 | total = ndirty + nmuzzy + nretained; | ||
| 742 | total_bytes = dirty_bytes + muzzy_bytes + retained_bytes; | ||
| 743 | |||
| 744 | in_gap_prev = in_gap; | ||
| 745 | in_gap = (total == 0); | ||
| 746 | |||
| 747 | if (in_gap_prev && !in_gap) { | ||
| 748 | emitter_table_printf(emitter, | ||
| 749 | " ---\n"); | ||
| 750 | } | ||
| 751 | |||
| 752 | emitter_json_object_begin(emitter); | ||
| 753 | emitter_json_kv(emitter, "ndirty", emitter_type_size, &ndirty); | ||
| 754 | emitter_json_kv(emitter, "nmuzzy", emitter_type_size, &nmuzzy); | ||
| 755 | emitter_json_kv(emitter, "nretained", emitter_type_size, | ||
| 756 | &nretained); | ||
| 757 | |||
| 758 | emitter_json_kv(emitter, "dirty_bytes", emitter_type_size, | ||
| 759 | &dirty_bytes); | ||
| 760 | emitter_json_kv(emitter, "muzzy_bytes", emitter_type_size, | ||
| 761 | &muzzy_bytes); | ||
| 762 | emitter_json_kv(emitter, "retained_bytes", emitter_type_size, | ||
| 763 | &retained_bytes); | ||
| 764 | emitter_json_object_end(emitter); | ||
| 765 | |||
| 766 | col_size.size_val = sz_pind2sz(j); | ||
| 767 | col_ind.size_val = j; | ||
| 768 | col_ndirty.size_val = ndirty; | ||
| 769 | col_dirty.size_val = dirty_bytes; | ||
| 770 | col_nmuzzy.size_val = nmuzzy; | ||
| 771 | col_muzzy.size_val = muzzy_bytes; | ||
| 772 | col_nretained.size_val = nretained; | ||
| 773 | col_retained.size_val = retained_bytes; | ||
| 774 | col_ntotal.size_val = total; | ||
| 775 | col_total.size_val = total_bytes; | ||
| 776 | |||
| 777 | if (!in_gap) { | ||
| 778 | emitter_table_row(emitter, &row); | ||
| 779 | } | ||
| 780 | } | ||
| 781 | emitter_json_array_end(emitter); /* Close "extents". */ | ||
| 782 | if (in_gap) { | ||
| 783 | emitter_table_printf(emitter, " ---\n"); | ||
| 784 | } | ||
| 785 | } | ||
| 786 | |||
| 787 | static void | ||
| 788 | stats_arena_hpa_shard_print(emitter_t *emitter, unsigned i, uint64_t uptime) { | ||
| 789 | emitter_row_t header_row; | ||
| 790 | emitter_row_init(&header_row); | ||
| 791 | emitter_row_t row; | ||
| 792 | emitter_row_init(&row); | ||
| 793 | |||
| 794 | uint64_t npurge_passes; | ||
| 795 | uint64_t npurges; | ||
| 796 | uint64_t nhugifies; | ||
| 797 | uint64_t ndehugifies; | ||
| 798 | |||
| 799 | CTL_M2_GET("stats.arenas.0.hpa_shard.npurge_passes", | ||
| 800 | i, &npurge_passes, uint64_t); | ||
| 801 | CTL_M2_GET("stats.arenas.0.hpa_shard.npurges", | ||
| 802 | i, &npurges, uint64_t); | ||
| 803 | CTL_M2_GET("stats.arenas.0.hpa_shard.nhugifies", | ||
| 804 | i, &nhugifies, uint64_t); | ||
| 805 | CTL_M2_GET("stats.arenas.0.hpa_shard.ndehugifies", | ||
| 806 | i, &ndehugifies, uint64_t); | ||
| 807 | |||
| 808 | size_t npageslabs_huge; | ||
| 809 | size_t nactive_huge; | ||
| 810 | size_t ndirty_huge; | ||
| 811 | |||
| 812 | size_t npageslabs_nonhuge; | ||
| 813 | size_t nactive_nonhuge; | ||
| 814 | size_t ndirty_nonhuge; | ||
| 815 | size_t nretained_nonhuge; | ||
| 816 | |||
| 817 | size_t sec_bytes; | ||
| 818 | CTL_M2_GET("stats.arenas.0.hpa_sec_bytes", i, &sec_bytes, size_t); | ||
| 819 | emitter_kv(emitter, "sec_bytes", "Bytes in small extent cache", | ||
| 820 | emitter_type_size, &sec_bytes); | ||
| 821 | |||
| 822 | /* First, global stats. */ | ||
| 823 | emitter_table_printf(emitter, | ||
| 824 | "HPA shard stats:\n" | ||
| 825 | " Purge passes: %" FMTu64 " (%" FMTu64 " / sec)\n" | ||
| 826 | " Purges: %" FMTu64 " (%" FMTu64 " / sec)\n" | ||
| 827 | " Hugeifies: %" FMTu64 " (%" FMTu64 " / sec)\n" | ||
| 828 | " Dehugifies: %" FMTu64 " (%" FMTu64 " / sec)\n" | ||
| 829 | "\n", | ||
| 830 | npurge_passes, rate_per_second(npurge_passes, uptime), | ||
| 831 | npurges, rate_per_second(npurges, uptime), | ||
| 832 | nhugifies, rate_per_second(nhugifies, uptime), | ||
| 833 | ndehugifies, rate_per_second(ndehugifies, uptime)); | ||
| 834 | |||
| 835 | emitter_json_object_kv_begin(emitter, "hpa_shard"); | ||
| 836 | emitter_json_kv(emitter, "npurge_passes", emitter_type_uint64, | ||
| 837 | &npurge_passes); | ||
| 838 | emitter_json_kv(emitter, "npurges", emitter_type_uint64, | ||
| 839 | &npurges); | ||
| 840 | emitter_json_kv(emitter, "nhugifies", emitter_type_uint64, | ||
| 841 | &nhugifies); | ||
| 842 | emitter_json_kv(emitter, "ndehugifies", emitter_type_uint64, | ||
| 843 | &ndehugifies); | ||
| 844 | |||
| 845 | /* Next, full slab stats. */ | ||
| 846 | CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.npageslabs_huge", | ||
| 847 | i, &npageslabs_huge, size_t); | ||
| 848 | CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.nactive_huge", | ||
| 849 | i, &nactive_huge, size_t); | ||
| 850 | CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.ndirty_huge", | ||
| 851 | i, &ndirty_huge, size_t); | ||
| 852 | |||
| 853 | CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.npageslabs_nonhuge", | ||
| 854 | i, &npageslabs_nonhuge, size_t); | ||
| 855 | CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.nactive_nonhuge", | ||
| 856 | i, &nactive_nonhuge, size_t); | ||
| 857 | CTL_M2_GET("stats.arenas.0.hpa_shard.full_slabs.ndirty_nonhuge", | ||
| 858 | i, &ndirty_nonhuge, size_t); | ||
| 859 | nretained_nonhuge = npageslabs_nonhuge * HUGEPAGE_PAGES | ||
| 860 | - nactive_nonhuge - ndirty_nonhuge; | ||
| 861 | |||
| 862 | emitter_table_printf(emitter, | ||
| 863 | " In full slabs:\n" | ||
| 864 | " npageslabs: %zu huge, %zu nonhuge\n" | ||
| 865 | " nactive: %zu huge, %zu nonhuge \n" | ||
| 866 | " ndirty: %zu huge, %zu nonhuge \n" | ||
| 867 | " nretained: 0 huge, %zu nonhuge \n", | ||
| 868 | npageslabs_huge, npageslabs_nonhuge, | ||
| 869 | nactive_huge, nactive_nonhuge, | ||
| 870 | ndirty_huge, ndirty_nonhuge, | ||
| 871 | nretained_nonhuge); | ||
| 872 | |||
| 873 | emitter_json_object_kv_begin(emitter, "full_slabs"); | ||
| 874 | emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size, | ||
| 875 | &npageslabs_huge); | ||
| 876 | emitter_json_kv(emitter, "nactive_huge", emitter_type_size, | ||
| 877 | &nactive_huge); | ||
| 878 | emitter_json_kv(emitter, "nactive_huge", emitter_type_size, | ||
| 879 | &nactive_huge); | ||
| 880 | emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size, | ||
| 881 | &npageslabs_nonhuge); | ||
| 882 | emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size, | ||
| 883 | &nactive_nonhuge); | ||
| 884 | emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size, | ||
| 885 | &ndirty_nonhuge); | ||
| 886 | emitter_json_object_end(emitter); /* End "full_slabs" */ | ||
| 887 | |||
| 888 | /* Next, empty slab stats. */ | ||
| 889 | CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.npageslabs_huge", | ||
| 890 | i, &npageslabs_huge, size_t); | ||
| 891 | CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.nactive_huge", | ||
| 892 | i, &nactive_huge, size_t); | ||
| 893 | CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.ndirty_huge", | ||
| 894 | i, &ndirty_huge, size_t); | ||
| 895 | |||
| 896 | CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.npageslabs_nonhuge", | ||
| 897 | i, &npageslabs_nonhuge, size_t); | ||
| 898 | CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.nactive_nonhuge", | ||
| 899 | i, &nactive_nonhuge, size_t); | ||
| 900 | CTL_M2_GET("stats.arenas.0.hpa_shard.empty_slabs.ndirty_nonhuge", | ||
| 901 | i, &ndirty_nonhuge, size_t); | ||
| 902 | nretained_nonhuge = npageslabs_nonhuge * HUGEPAGE_PAGES | ||
| 903 | - nactive_nonhuge - ndirty_nonhuge; | ||
| 904 | |||
| 905 | emitter_table_printf(emitter, | ||
| 906 | " In empty slabs:\n" | ||
| 907 | " npageslabs: %zu huge, %zu nonhuge\n" | ||
| 908 | " nactive: %zu huge, %zu nonhuge \n" | ||
| 909 | " ndirty: %zu huge, %zu nonhuge \n" | ||
| 910 | " nretained: 0 huge, %zu nonhuge \n" | ||
| 911 | "\n", | ||
| 912 | npageslabs_huge, npageslabs_nonhuge, | ||
| 913 | nactive_huge, nactive_nonhuge, | ||
| 914 | ndirty_huge, ndirty_nonhuge, | ||
| 915 | nretained_nonhuge); | ||
| 916 | |||
| 917 | emitter_json_object_kv_begin(emitter, "empty_slabs"); | ||
| 918 | emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size, | ||
| 919 | &npageslabs_huge); | ||
| 920 | emitter_json_kv(emitter, "nactive_huge", emitter_type_size, | ||
| 921 | &nactive_huge); | ||
| 922 | emitter_json_kv(emitter, "nactive_huge", emitter_type_size, | ||
| 923 | &nactive_huge); | ||
| 924 | emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size, | ||
| 925 | &npageslabs_nonhuge); | ||
| 926 | emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size, | ||
| 927 | &nactive_nonhuge); | ||
| 928 | emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size, | ||
| 929 | &ndirty_nonhuge); | ||
| 930 | emitter_json_object_end(emitter); /* End "empty_slabs" */ | ||
| 931 | |||
| 932 | COL_HDR(row, size, NULL, right, 20, size) | ||
| 933 | COL_HDR(row, ind, NULL, right, 4, unsigned) | ||
| 934 | COL_HDR(row, npageslabs_huge, NULL, right, 16, size) | ||
| 935 | COL_HDR(row, nactive_huge, NULL, right, 16, size) | ||
| 936 | COL_HDR(row, ndirty_huge, NULL, right, 16, size) | ||
| 937 | COL_HDR(row, npageslabs_nonhuge, NULL, right, 20, size) | ||
| 938 | COL_HDR(row, nactive_nonhuge, NULL, right, 20, size) | ||
| 939 | COL_HDR(row, ndirty_nonhuge, NULL, right, 20, size) | ||
| 940 | COL_HDR(row, nretained_nonhuge, NULL, right, 20, size) | ||
| 941 | |||
| 942 | size_t stats_arenas_mib[CTL_MAX_DEPTH]; | ||
| 943 | CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); | ||
| 944 | stats_arenas_mib[2] = i; | ||
| 945 | CTL_LEAF_PREPARE(stats_arenas_mib, 3, "hpa_shard.nonfull_slabs"); | ||
| 946 | |||
| 947 | emitter_table_row(emitter, &header_row); | ||
| 948 | emitter_json_array_kv_begin(emitter, "nonfull_slabs"); | ||
| 949 | bool in_gap = false; | ||
| 950 | for (pszind_t j = 0; j < PSSET_NPSIZES && j < SC_NPSIZES; j++) { | ||
| 951 | stats_arenas_mib[5] = j; | ||
| 952 | |||
| 953 | CTL_LEAF(stats_arenas_mib, 6, "npageslabs_huge", | ||
| 954 | &npageslabs_huge, size_t); | ||
| 955 | CTL_LEAF(stats_arenas_mib, 6, "nactive_huge", | ||
| 956 | &nactive_huge, size_t); | ||
| 957 | CTL_LEAF(stats_arenas_mib, 6, "ndirty_huge", | ||
| 958 | &ndirty_huge, size_t); | ||
| 959 | |||
| 960 | CTL_LEAF(stats_arenas_mib, 6, "npageslabs_nonhuge", | ||
| 961 | &npageslabs_nonhuge, size_t); | ||
| 962 | CTL_LEAF(stats_arenas_mib, 6, "nactive_nonhuge", | ||
| 963 | &nactive_nonhuge, size_t); | ||
| 964 | CTL_LEAF(stats_arenas_mib, 6, "ndirty_nonhuge", | ||
| 965 | &ndirty_nonhuge, size_t); | ||
| 966 | nretained_nonhuge = npageslabs_nonhuge * HUGEPAGE_PAGES | ||
| 967 | - nactive_nonhuge - ndirty_nonhuge; | ||
| 968 | |||
| 969 | bool in_gap_prev = in_gap; | ||
| 970 | in_gap = (npageslabs_huge == 0 && npageslabs_nonhuge == 0); | ||
| 971 | if (in_gap_prev && !in_gap) { | ||
| 972 | emitter_table_printf(emitter, | ||
| 973 | " ---\n"); | ||
| 974 | } | ||
| 975 | |||
| 976 | col_size.size_val = sz_pind2sz(j); | ||
| 977 | col_ind.size_val = j; | ||
| 978 | col_npageslabs_huge.size_val = npageslabs_huge; | ||
| 979 | col_nactive_huge.size_val = nactive_huge; | ||
| 980 | col_ndirty_huge.size_val = ndirty_huge; | ||
| 981 | col_npageslabs_nonhuge.size_val = npageslabs_nonhuge; | ||
| 982 | col_nactive_nonhuge.size_val = nactive_nonhuge; | ||
| 983 | col_ndirty_nonhuge.size_val = ndirty_nonhuge; | ||
| 984 | col_nretained_nonhuge.size_val = nretained_nonhuge; | ||
| 985 | if (!in_gap) { | ||
| 986 | emitter_table_row(emitter, &row); | ||
| 987 | } | ||
| 988 | |||
| 989 | emitter_json_object_begin(emitter); | ||
| 990 | emitter_json_kv(emitter, "npageslabs_huge", emitter_type_size, | ||
| 991 | &npageslabs_huge); | ||
| 992 | emitter_json_kv(emitter, "nactive_huge", emitter_type_size, | ||
| 993 | &nactive_huge); | ||
| 994 | emitter_json_kv(emitter, "ndirty_huge", emitter_type_size, | ||
| 995 | &ndirty_huge); | ||
| 996 | emitter_json_kv(emitter, "npageslabs_nonhuge", emitter_type_size, | ||
| 997 | &npageslabs_nonhuge); | ||
| 998 | emitter_json_kv(emitter, "nactive_nonhuge", emitter_type_size, | ||
| 999 | &nactive_nonhuge); | ||
| 1000 | emitter_json_kv(emitter, "ndirty_nonhuge", emitter_type_size, | ||
| 1001 | &ndirty_nonhuge); | ||
| 1002 | emitter_json_object_end(emitter); | ||
| 1003 | } | ||
| 1004 | emitter_json_array_end(emitter); /* End "nonfull_slabs" */ | ||
| 1005 | emitter_json_object_end(emitter); /* End "hpa_shard" */ | ||
| 1006 | if (in_gap) { | ||
| 1007 | emitter_table_printf(emitter, " ---\n"); | ||
| 1008 | } | ||
| 1009 | } | ||
| 1010 | |||
| 1011 | static void | ||
| 1012 | stats_arena_mutexes_print(emitter_t *emitter, unsigned arena_ind, uint64_t uptime) { | ||
| 1013 | emitter_row_t row; | ||
| 1014 | emitter_col_t col_name; | ||
| 1015 | emitter_col_t col64[mutex_prof_num_uint64_t_counters]; | ||
| 1016 | emitter_col_t col32[mutex_prof_num_uint32_t_counters]; | ||
| 1017 | |||
| 1018 | emitter_row_init(&row); | ||
| 1019 | mutex_stats_init_cols(&row, "", &col_name, col64, col32); | ||
| 1020 | |||
| 1021 | emitter_json_object_kv_begin(emitter, "mutexes"); | ||
| 1022 | emitter_table_row(emitter, &row); | ||
| 1023 | |||
| 1024 | size_t stats_arenas_mib[CTL_MAX_DEPTH]; | ||
| 1025 | CTL_LEAF_PREPARE(stats_arenas_mib, 0, "stats.arenas"); | ||
| 1026 | stats_arenas_mib[2] = arena_ind; | ||
| 1027 | CTL_LEAF_PREPARE(stats_arenas_mib, 3, "mutexes"); | ||
| 1028 | |||
| 1029 | for (mutex_prof_arena_ind_t i = 0; i < mutex_prof_num_arena_mutexes; | ||
| 1030 | i++) { | ||
| 1031 | const char *name = arena_mutex_names[i]; | ||
| 1032 | emitter_json_object_kv_begin(emitter, name); | ||
| 1033 | mutex_stats_read_arena(stats_arenas_mib, 4, name, &col_name, | ||
| 1034 | col64, col32, uptime); | ||
| 1035 | mutex_stats_emit(emitter, &row, col64, col32); | ||
| 1036 | emitter_json_object_end(emitter); /* Close the mutex dict. */ | ||
| 1037 | } | ||
| 1038 | emitter_json_object_end(emitter); /* End "mutexes". */ | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | JEMALLOC_COLD | ||
| 1042 | static void | ||
| 1043 | stats_arena_print(emitter_t *emitter, unsigned i, bool bins, bool large, | ||
| 1044 | bool mutex, bool extents, bool hpa) { | ||
| 1045 | unsigned nthreads; | ||
| 1046 | const char *dss; | ||
| 1047 | ssize_t dirty_decay_ms, muzzy_decay_ms; | ||
| 1048 | size_t page, pactive, pdirty, pmuzzy, mapped, retained; | ||
| 1049 | size_t base, internal, resident, metadata_thp, extent_avail; | ||
| 1050 | uint64_t dirty_npurge, dirty_nmadvise, dirty_purged; | ||
| 1051 | uint64_t muzzy_npurge, muzzy_nmadvise, muzzy_purged; | ||
| 1052 | size_t small_allocated; | ||
| 1053 | uint64_t small_nmalloc, small_ndalloc, small_nrequests, small_nfills, | ||
| 1054 | small_nflushes; | ||
| 1055 | size_t large_allocated; | ||
| 1056 | uint64_t large_nmalloc, large_ndalloc, large_nrequests, large_nfills, | ||
| 1057 | large_nflushes; | ||
| 1058 | size_t tcache_bytes, tcache_stashed_bytes, abandoned_vm; | ||
| 1059 | uint64_t uptime; | ||
| 1060 | |||
| 1061 | CTL_GET("arenas.page", &page, size_t); | ||
| 1062 | |||
| 1063 | CTL_M2_GET("stats.arenas.0.nthreads", i, &nthreads, unsigned); | ||
| 1064 | emitter_kv(emitter, "nthreads", "assigned threads", | ||
| 1065 | emitter_type_unsigned, &nthreads); | ||
| 1066 | |||
| 1067 | CTL_M2_GET("stats.arenas.0.uptime", i, &uptime, uint64_t); | ||
| 1068 | emitter_kv(emitter, "uptime_ns", "uptime", emitter_type_uint64, | ||
| 1069 | &uptime); | ||
| 1070 | |||
| 1071 | CTL_M2_GET("stats.arenas.0.dss", i, &dss, const char *); | ||
| 1072 | emitter_kv(emitter, "dss", "dss allocation precedence", | ||
| 1073 | emitter_type_string, &dss); | ||
| 1074 | |||
| 1075 | CTL_M2_GET("stats.arenas.0.dirty_decay_ms", i, &dirty_decay_ms, | ||
| 1076 | ssize_t); | ||
| 1077 | CTL_M2_GET("stats.arenas.0.muzzy_decay_ms", i, &muzzy_decay_ms, | ||
| 1078 | ssize_t); | ||
| 1079 | CTL_M2_GET("stats.arenas.0.pactive", i, &pactive, size_t); | ||
| 1080 | CTL_M2_GET("stats.arenas.0.pdirty", i, &pdirty, size_t); | ||
| 1081 | CTL_M2_GET("stats.arenas.0.pmuzzy", i, &pmuzzy, size_t); | ||
| 1082 | CTL_M2_GET("stats.arenas.0.dirty_npurge", i, &dirty_npurge, uint64_t); | ||
| 1083 | CTL_M2_GET("stats.arenas.0.dirty_nmadvise", i, &dirty_nmadvise, | ||
| 1084 | uint64_t); | ||
| 1085 | CTL_M2_GET("stats.arenas.0.dirty_purged", i, &dirty_purged, uint64_t); | ||
| 1086 | CTL_M2_GET("stats.arenas.0.muzzy_npurge", i, &muzzy_npurge, uint64_t); | ||
| 1087 | CTL_M2_GET("stats.arenas.0.muzzy_nmadvise", i, &muzzy_nmadvise, | ||
| 1088 | uint64_t); | ||
| 1089 | CTL_M2_GET("stats.arenas.0.muzzy_purged", i, &muzzy_purged, uint64_t); | ||
| 1090 | |||
| 1091 | emitter_row_t decay_row; | ||
| 1092 | emitter_row_init(&decay_row); | ||
| 1093 | |||
| 1094 | /* JSON-style emission. */ | ||
| 1095 | emitter_json_kv(emitter, "dirty_decay_ms", emitter_type_ssize, | ||
| 1096 | &dirty_decay_ms); | ||
| 1097 | emitter_json_kv(emitter, "muzzy_decay_ms", emitter_type_ssize, | ||
| 1098 | &muzzy_decay_ms); | ||
| 1099 | |||
| 1100 | emitter_json_kv(emitter, "pactive", emitter_type_size, &pactive); | ||
| 1101 | emitter_json_kv(emitter, "pdirty", emitter_type_size, &pdirty); | ||
| 1102 | emitter_json_kv(emitter, "pmuzzy", emitter_type_size, &pmuzzy); | ||
| 1103 | |||
| 1104 | emitter_json_kv(emitter, "dirty_npurge", emitter_type_uint64, | ||
| 1105 | &dirty_npurge); | ||
| 1106 | emitter_json_kv(emitter, "dirty_nmadvise", emitter_type_uint64, | ||
| 1107 | &dirty_nmadvise); | ||
| 1108 | emitter_json_kv(emitter, "dirty_purged", emitter_type_uint64, | ||
| 1109 | &dirty_purged); | ||
| 1110 | |||
| 1111 | emitter_json_kv(emitter, "muzzy_npurge", emitter_type_uint64, | ||
| 1112 | &muzzy_npurge); | ||
| 1113 | emitter_json_kv(emitter, "muzzy_nmadvise", emitter_type_uint64, | ||
| 1114 | &muzzy_nmadvise); | ||
| 1115 | emitter_json_kv(emitter, "muzzy_purged", emitter_type_uint64, | ||
| 1116 | &muzzy_purged); | ||
| 1117 | |||
| 1118 | /* Table-style emission. */ | ||
| 1119 | COL(decay_row, decay_type, right, 9, title); | ||
| 1120 | col_decay_type.str_val = "decaying:"; | ||
| 1121 | |||
| 1122 | COL(decay_row, decay_time, right, 6, title); | ||
| 1123 | col_decay_time.str_val = "time"; | ||
| 1124 | |||
| 1125 | COL(decay_row, decay_npages, right, 13, title); | ||
| 1126 | col_decay_npages.str_val = "npages"; | ||
| 1127 | |||
| 1128 | COL(decay_row, decay_sweeps, right, 13, title); | ||
| 1129 | col_decay_sweeps.str_val = "sweeps"; | ||
| 1130 | |||
| 1131 | COL(decay_row, decay_madvises, right, 13, title); | ||
| 1132 | col_decay_madvises.str_val = "madvises"; | ||
| 1133 | |||
| 1134 | COL(decay_row, decay_purged, right, 13, title); | ||
| 1135 | col_decay_purged.str_val = "purged"; | ||
| 1136 | |||
| 1137 | /* Title row. */ | ||
| 1138 | emitter_table_row(emitter, &decay_row); | ||
| 1139 | |||
| 1140 | /* Dirty row. */ | ||
| 1141 | col_decay_type.str_val = "dirty:"; | ||
| 1142 | |||
| 1143 | if (dirty_decay_ms >= 0) { | ||
| 1144 | col_decay_time.type = emitter_type_ssize; | ||
| 1145 | col_decay_time.ssize_val = dirty_decay_ms; | ||
| 1146 | } else { | ||
| 1147 | col_decay_time.type = emitter_type_title; | ||
| 1148 | col_decay_time.str_val = "N/A"; | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | col_decay_npages.type = emitter_type_size; | ||
| 1152 | col_decay_npages.size_val = pdirty; | ||
| 1153 | |||
| 1154 | col_decay_sweeps.type = emitter_type_uint64; | ||
| 1155 | col_decay_sweeps.uint64_val = dirty_npurge; | ||
| 1156 | |||
| 1157 | col_decay_madvises.type = emitter_type_uint64; | ||
| 1158 | col_decay_madvises.uint64_val = dirty_nmadvise; | ||
| 1159 | |||
| 1160 | col_decay_purged.type = emitter_type_uint64; | ||
| 1161 | col_decay_purged.uint64_val = dirty_purged; | ||
| 1162 | |||
| 1163 | emitter_table_row(emitter, &decay_row); | ||
| 1164 | |||
| 1165 | /* Muzzy row. */ | ||
| 1166 | col_decay_type.str_val = "muzzy:"; | ||
| 1167 | |||
| 1168 | if (muzzy_decay_ms >= 0) { | ||
| 1169 | col_decay_time.type = emitter_type_ssize; | ||
| 1170 | col_decay_time.ssize_val = muzzy_decay_ms; | ||
| 1171 | } else { | ||
| 1172 | col_decay_time.type = emitter_type_title; | ||
| 1173 | col_decay_time.str_val = "N/A"; | ||
| 1174 | } | ||
| 1175 | |||
| 1176 | col_decay_npages.type = emitter_type_size; | ||
| 1177 | col_decay_npages.size_val = pmuzzy; | ||
| 1178 | |||
| 1179 | col_decay_sweeps.type = emitter_type_uint64; | ||
| 1180 | col_decay_sweeps.uint64_val = muzzy_npurge; | ||
| 1181 | |||
| 1182 | col_decay_madvises.type = emitter_type_uint64; | ||
| 1183 | col_decay_madvises.uint64_val = muzzy_nmadvise; | ||
| 1184 | |||
| 1185 | col_decay_purged.type = emitter_type_uint64; | ||
| 1186 | col_decay_purged.uint64_val = muzzy_purged; | ||
| 1187 | |||
| 1188 | emitter_table_row(emitter, &decay_row); | ||
| 1189 | |||
| 1190 | /* Small / large / total allocation counts. */ | ||
| 1191 | emitter_row_t alloc_count_row; | ||
| 1192 | emitter_row_init(&alloc_count_row); | ||
| 1193 | |||
| 1194 | COL(alloc_count_row, count_title, left, 21, title); | ||
| 1195 | col_count_title.str_val = ""; | ||
| 1196 | |||
| 1197 | COL(alloc_count_row, count_allocated, right, 16, title); | ||
| 1198 | col_count_allocated.str_val = "allocated"; | ||
| 1199 | |||
| 1200 | COL(alloc_count_row, count_nmalloc, right, 16, title); | ||
| 1201 | col_count_nmalloc.str_val = "nmalloc"; | ||
| 1202 | COL(alloc_count_row, count_nmalloc_ps, right, 10, title); | ||
| 1203 | col_count_nmalloc_ps.str_val = "(#/sec)"; | ||
| 1204 | |||
| 1205 | COL(alloc_count_row, count_ndalloc, right, 16, title); | ||
| 1206 | col_count_ndalloc.str_val = "ndalloc"; | ||
| 1207 | COL(alloc_count_row, count_ndalloc_ps, right, 10, title); | ||
| 1208 | col_count_ndalloc_ps.str_val = "(#/sec)"; | ||
| 1209 | |||
| 1210 | COL(alloc_count_row, count_nrequests, right, 16, title); | ||
| 1211 | col_count_nrequests.str_val = "nrequests"; | ||
| 1212 | COL(alloc_count_row, count_nrequests_ps, right, 10, title); | ||
| 1213 | col_count_nrequests_ps.str_val = "(#/sec)"; | ||
| 1214 | |||
| 1215 | COL(alloc_count_row, count_nfills, right, 16, title); | ||
| 1216 | col_count_nfills.str_val = "nfill"; | ||
| 1217 | COL(alloc_count_row, count_nfills_ps, right, 10, title); | ||
| 1218 | col_count_nfills_ps.str_val = "(#/sec)"; | ||
| 1219 | |||
| 1220 | COL(alloc_count_row, count_nflushes, right, 16, title); | ||
| 1221 | col_count_nflushes.str_val = "nflush"; | ||
| 1222 | COL(alloc_count_row, count_nflushes_ps, right, 10, title); | ||
| 1223 | col_count_nflushes_ps.str_val = "(#/sec)"; | ||
| 1224 | |||
| 1225 | emitter_table_row(emitter, &alloc_count_row); | ||
| 1226 | |||
| 1227 | col_count_nmalloc_ps.type = emitter_type_uint64; | ||
| 1228 | col_count_ndalloc_ps.type = emitter_type_uint64; | ||
| 1229 | col_count_nrequests_ps.type = emitter_type_uint64; | ||
| 1230 | col_count_nfills_ps.type = emitter_type_uint64; | ||
| 1231 | col_count_nflushes_ps.type = emitter_type_uint64; | ||
| 1232 | |||
| 1233 | #define GET_AND_EMIT_ALLOC_STAT(small_or_large, name, valtype) \ | ||
| 1234 | CTL_M2_GET("stats.arenas.0." #small_or_large "." #name, i, \ | ||
| 1235 | &small_or_large##_##name, valtype##_t); \ | ||
| 1236 | emitter_json_kv(emitter, #name, emitter_type_##valtype, \ | ||
| 1237 | &small_or_large##_##name); \ | ||
| 1238 | col_count_##name.type = emitter_type_##valtype; \ | ||
| 1239 | col_count_##name.valtype##_val = small_or_large##_##name; | ||
| 1240 | |||
| 1241 | emitter_json_object_kv_begin(emitter, "small"); | ||
| 1242 | col_count_title.str_val = "small:"; | ||
| 1243 | |||
| 1244 | GET_AND_EMIT_ALLOC_STAT(small, allocated, size) | ||
| 1245 | GET_AND_EMIT_ALLOC_STAT(small, nmalloc, uint64) | ||
| 1246 | col_count_nmalloc_ps.uint64_val = | ||
| 1247 | rate_per_second(col_count_nmalloc.uint64_val, uptime); | ||
| 1248 | GET_AND_EMIT_ALLOC_STAT(small, ndalloc, uint64) | ||
| 1249 | col_count_ndalloc_ps.uint64_val = | ||
| 1250 | rate_per_second(col_count_ndalloc.uint64_val, uptime); | ||
| 1251 | GET_AND_EMIT_ALLOC_STAT(small, nrequests, uint64) | ||
| 1252 | col_count_nrequests_ps.uint64_val = | ||
| 1253 | rate_per_second(col_count_nrequests.uint64_val, uptime); | ||
| 1254 | GET_AND_EMIT_ALLOC_STAT(small, nfills, uint64) | ||
| 1255 | col_count_nfills_ps.uint64_val = | ||
| 1256 | rate_per_second(col_count_nfills.uint64_val, uptime); | ||
| 1257 | GET_AND_EMIT_ALLOC_STAT(small, nflushes, uint64) | ||
| 1258 | col_count_nflushes_ps.uint64_val = | ||
| 1259 | rate_per_second(col_count_nflushes.uint64_val, uptime); | ||
| 1260 | |||
| 1261 | emitter_table_row(emitter, &alloc_count_row); | ||
| 1262 | emitter_json_object_end(emitter); /* Close "small". */ | ||
| 1263 | |||
| 1264 | emitter_json_object_kv_begin(emitter, "large"); | ||
| 1265 | col_count_title.str_val = "large:"; | ||
| 1266 | |||
| 1267 | GET_AND_EMIT_ALLOC_STAT(large, allocated, size) | ||
| 1268 | GET_AND_EMIT_ALLOC_STAT(large, nmalloc, uint64) | ||
| 1269 | col_count_nmalloc_ps.uint64_val = | ||
| 1270 | rate_per_second(col_count_nmalloc.uint64_val, uptime); | ||
| 1271 | GET_AND_EMIT_ALLOC_STAT(large, ndalloc, uint64) | ||
| 1272 | col_count_ndalloc_ps.uint64_val = | ||
| 1273 | rate_per_second(col_count_ndalloc.uint64_val, uptime); | ||
| 1274 | GET_AND_EMIT_ALLOC_STAT(large, nrequests, uint64) | ||
| 1275 | col_count_nrequests_ps.uint64_val = | ||
| 1276 | rate_per_second(col_count_nrequests.uint64_val, uptime); | ||
| 1277 | GET_AND_EMIT_ALLOC_STAT(large, nfills, uint64) | ||
| 1278 | col_count_nfills_ps.uint64_val = | ||
| 1279 | rate_per_second(col_count_nfills.uint64_val, uptime); | ||
| 1280 | GET_AND_EMIT_ALLOC_STAT(large, nflushes, uint64) | ||
| 1281 | col_count_nflushes_ps.uint64_val = | ||
| 1282 | rate_per_second(col_count_nflushes.uint64_val, uptime); | ||
| 1283 | |||
| 1284 | emitter_table_row(emitter, &alloc_count_row); | ||
| 1285 | emitter_json_object_end(emitter); /* Close "large". */ | ||
| 1286 | |||
| 1287 | #undef GET_AND_EMIT_ALLOC_STAT | ||
| 1288 | |||
| 1289 | /* Aggregated small + large stats are emitter only in table mode. */ | ||
| 1290 | col_count_title.str_val = "total:"; | ||
| 1291 | col_count_allocated.size_val = small_allocated + large_allocated; | ||
| 1292 | col_count_nmalloc.uint64_val = small_nmalloc + large_nmalloc; | ||
| 1293 | col_count_ndalloc.uint64_val = small_ndalloc + large_ndalloc; | ||
| 1294 | col_count_nrequests.uint64_val = small_nrequests + large_nrequests; | ||
| 1295 | col_count_nfills.uint64_val = small_nfills + large_nfills; | ||
| 1296 | col_count_nflushes.uint64_val = small_nflushes + large_nflushes; | ||
| 1297 | col_count_nmalloc_ps.uint64_val = | ||
| 1298 | rate_per_second(col_count_nmalloc.uint64_val, uptime); | ||
| 1299 | col_count_ndalloc_ps.uint64_val = | ||
| 1300 | rate_per_second(col_count_ndalloc.uint64_val, uptime); | ||
| 1301 | col_count_nrequests_ps.uint64_val = | ||
| 1302 | rate_per_second(col_count_nrequests.uint64_val, uptime); | ||
| 1303 | col_count_nfills_ps.uint64_val = | ||
| 1304 | rate_per_second(col_count_nfills.uint64_val, uptime); | ||
| 1305 | col_count_nflushes_ps.uint64_val = | ||
| 1306 | rate_per_second(col_count_nflushes.uint64_val, uptime); | ||
| 1307 | emitter_table_row(emitter, &alloc_count_row); | ||
| 1308 | |||
| 1309 | emitter_row_t mem_count_row; | ||
| 1310 | emitter_row_init(&mem_count_row); | ||
| 1311 | |||
| 1312 | emitter_col_t mem_count_title; | ||
| 1313 | emitter_col_init(&mem_count_title, &mem_count_row); | ||
| 1314 | mem_count_title.justify = emitter_justify_left; | ||
| 1315 | mem_count_title.width = 21; | ||
| 1316 | mem_count_title.type = emitter_type_title; | ||
| 1317 | mem_count_title.str_val = ""; | ||
| 1318 | |||
| 1319 | emitter_col_t mem_count_val; | ||
| 1320 | emitter_col_init(&mem_count_val, &mem_count_row); | ||
| 1321 | mem_count_val.justify = emitter_justify_right; | ||
| 1322 | mem_count_val.width = 16; | ||
| 1323 | mem_count_val.type = emitter_type_title; | ||
| 1324 | mem_count_val.str_val = ""; | ||
| 1325 | |||
| 1326 | emitter_table_row(emitter, &mem_count_row); | ||
| 1327 | mem_count_val.type = emitter_type_size; | ||
| 1328 | |||
| 1329 | /* Active count in bytes is emitted only in table mode. */ | ||
| 1330 | mem_count_title.str_val = "active:"; | ||
| 1331 | mem_count_val.size_val = pactive * page; | ||
| 1332 | emitter_table_row(emitter, &mem_count_row); | ||
| 1333 | |||
| 1334 | #define GET_AND_EMIT_MEM_STAT(stat) \ | ||
| 1335 | CTL_M2_GET("stats.arenas.0."#stat, i, &stat, size_t); \ | ||
| 1336 | emitter_json_kv(emitter, #stat, emitter_type_size, &stat); \ | ||
| 1337 | mem_count_title.str_val = #stat":"; \ | ||
| 1338 | mem_count_val.size_val = stat; \ | ||
| 1339 | emitter_table_row(emitter, &mem_count_row); | ||
| 1340 | |||
| 1341 | GET_AND_EMIT_MEM_STAT(mapped) | ||
| 1342 | GET_AND_EMIT_MEM_STAT(retained) | ||
| 1343 | GET_AND_EMIT_MEM_STAT(base) | ||
| 1344 | GET_AND_EMIT_MEM_STAT(internal) | ||
| 1345 | GET_AND_EMIT_MEM_STAT(metadata_thp) | ||
| 1346 | GET_AND_EMIT_MEM_STAT(tcache_bytes) | ||
| 1347 | GET_AND_EMIT_MEM_STAT(tcache_stashed_bytes) | ||
| 1348 | GET_AND_EMIT_MEM_STAT(resident) | ||
| 1349 | GET_AND_EMIT_MEM_STAT(abandoned_vm) | ||
| 1350 | GET_AND_EMIT_MEM_STAT(extent_avail) | ||
| 1351 | #undef GET_AND_EMIT_MEM_STAT | ||
| 1352 | |||
| 1353 | if (mutex) { | ||
| 1354 | stats_arena_mutexes_print(emitter, i, uptime); | ||
| 1355 | } | ||
| 1356 | if (bins) { | ||
| 1357 | stats_arena_bins_print(emitter, mutex, i, uptime); | ||
| 1358 | } | ||
| 1359 | if (large) { | ||
| 1360 | stats_arena_lextents_print(emitter, i, uptime); | ||
| 1361 | } | ||
| 1362 | if (extents) { | ||
| 1363 | stats_arena_extents_print(emitter, i); | ||
| 1364 | } | ||
| 1365 | if (hpa) { | ||
| 1366 | stats_arena_hpa_shard_print(emitter, i, uptime); | ||
| 1367 | } | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | JEMALLOC_COLD | ||
| 1371 | static void | ||
| 1372 | stats_general_print(emitter_t *emitter) { | ||
| 1373 | const char *cpv; | ||
| 1374 | bool bv, bv2; | ||
| 1375 | unsigned uv; | ||
| 1376 | uint32_t u32v; | ||
| 1377 | uint64_t u64v; | ||
| 1378 | int64_t i64v; | ||
| 1379 | ssize_t ssv, ssv2; | ||
| 1380 | size_t sv, bsz, usz, u32sz, u64sz, i64sz, ssz, sssz, cpsz; | ||
| 1381 | |||
| 1382 | bsz = sizeof(bool); | ||
| 1383 | usz = sizeof(unsigned); | ||
| 1384 | ssz = sizeof(size_t); | ||
| 1385 | sssz = sizeof(ssize_t); | ||
| 1386 | cpsz = sizeof(const char *); | ||
| 1387 | u32sz = sizeof(uint32_t); | ||
| 1388 | i64sz = sizeof(int64_t); | ||
| 1389 | u64sz = sizeof(uint64_t); | ||
| 1390 | |||
| 1391 | CTL_GET("version", &cpv, const char *); | ||
| 1392 | emitter_kv(emitter, "version", "Version", emitter_type_string, &cpv); | ||
| 1393 | |||
| 1394 | /* config. */ | ||
| 1395 | emitter_dict_begin(emitter, "config", "Build-time option settings"); | ||
| 1396 | #define CONFIG_WRITE_BOOL(name) \ | ||
| 1397 | do { \ | ||
| 1398 | CTL_GET("config."#name, &bv, bool); \ | ||
| 1399 | emitter_kv(emitter, #name, "config."#name, \ | ||
| 1400 | emitter_type_bool, &bv); \ | ||
| 1401 | } while (0) | ||
| 1402 | |||
| 1403 | CONFIG_WRITE_BOOL(cache_oblivious); | ||
| 1404 | CONFIG_WRITE_BOOL(debug); | ||
| 1405 | CONFIG_WRITE_BOOL(fill); | ||
| 1406 | CONFIG_WRITE_BOOL(lazy_lock); | ||
| 1407 | emitter_kv(emitter, "malloc_conf", "config.malloc_conf", | ||
| 1408 | emitter_type_string, &config_malloc_conf); | ||
| 1409 | |||
| 1410 | CONFIG_WRITE_BOOL(opt_safety_checks); | ||
| 1411 | CONFIG_WRITE_BOOL(prof); | ||
| 1412 | CONFIG_WRITE_BOOL(prof_libgcc); | ||
| 1413 | CONFIG_WRITE_BOOL(prof_libunwind); | ||
| 1414 | CONFIG_WRITE_BOOL(stats); | ||
| 1415 | CONFIG_WRITE_BOOL(utrace); | ||
| 1416 | CONFIG_WRITE_BOOL(xmalloc); | ||
| 1417 | #undef CONFIG_WRITE_BOOL | ||
| 1418 | emitter_dict_end(emitter); /* Close "config" dict. */ | ||
| 1419 | |||
| 1420 | /* opt. */ | ||
| 1421 | #define OPT_WRITE(name, var, size, emitter_type) \ | ||
| 1422 | if (je_mallctl("opt."name, (void *)&var, &size, NULL, 0) == \ | ||
| 1423 | 0) { \ | ||
| 1424 | emitter_kv(emitter, name, "opt."name, emitter_type, \ | ||
| 1425 | &var); \ | ||
| 1426 | } | ||
| 1427 | |||
| 1428 | #define OPT_WRITE_MUTABLE(name, var1, var2, size, emitter_type, \ | ||
| 1429 | altname) \ | ||
| 1430 | if (je_mallctl("opt."name, (void *)&var1, &size, NULL, 0) == \ | ||
| 1431 | 0 && je_mallctl(altname, (void *)&var2, &size, NULL, 0) \ | ||
| 1432 | == 0) { \ | ||
| 1433 | emitter_kv_note(emitter, name, "opt."name, \ | ||
| 1434 | emitter_type, &var1, altname, emitter_type, \ | ||
| 1435 | &var2); \ | ||
| 1436 | } | ||
| 1437 | |||
| 1438 | #define OPT_WRITE_BOOL(name) OPT_WRITE(name, bv, bsz, emitter_type_bool) | ||
| 1439 | #define OPT_WRITE_BOOL_MUTABLE(name, altname) \ | ||
| 1440 | OPT_WRITE_MUTABLE(name, bv, bv2, bsz, emitter_type_bool, altname) | ||
| 1441 | |||
| 1442 | #define OPT_WRITE_UNSIGNED(name) \ | ||
| 1443 | OPT_WRITE(name, uv, usz, emitter_type_unsigned) | ||
| 1444 | |||
| 1445 | #define OPT_WRITE_INT64(name) \ | ||
| 1446 | OPT_WRITE(name, i64v, i64sz, emitter_type_int64) | ||
| 1447 | #define OPT_WRITE_UINT64(name) \ | ||
| 1448 | OPT_WRITE(name, u64v, u64sz, emitter_type_uint64) | ||
| 1449 | |||
| 1450 | #define OPT_WRITE_SIZE_T(name) \ | ||
| 1451 | OPT_WRITE(name, sv, ssz, emitter_type_size) | ||
| 1452 | #define OPT_WRITE_SSIZE_T(name) \ | ||
| 1453 | OPT_WRITE(name, ssv, sssz, emitter_type_ssize) | ||
| 1454 | #define OPT_WRITE_SSIZE_T_MUTABLE(name, altname) \ | ||
| 1455 | OPT_WRITE_MUTABLE(name, ssv, ssv2, sssz, emitter_type_ssize, \ | ||
| 1456 | altname) | ||
| 1457 | |||
| 1458 | #define OPT_WRITE_CHAR_P(name) \ | ||
| 1459 | OPT_WRITE(name, cpv, cpsz, emitter_type_string) | ||
| 1460 | |||
| 1461 | emitter_dict_begin(emitter, "opt", "Run-time option settings"); | ||
| 1462 | |||
| 1463 | OPT_WRITE_BOOL("abort") | ||
| 1464 | OPT_WRITE_BOOL("abort_conf") | ||
| 1465 | OPT_WRITE_BOOL("cache_oblivious") | ||
| 1466 | OPT_WRITE_BOOL("confirm_conf") | ||
| 1467 | OPT_WRITE_BOOL("retain") | ||
| 1468 | OPT_WRITE_CHAR_P("dss") | ||
| 1469 | OPT_WRITE_UNSIGNED("narenas") | ||
| 1470 | OPT_WRITE_CHAR_P("percpu_arena") | ||
| 1471 | OPT_WRITE_SIZE_T("oversize_threshold") | ||
| 1472 | OPT_WRITE_BOOL("hpa") | ||
| 1473 | OPT_WRITE_SIZE_T("hpa_slab_max_alloc") | ||
| 1474 | OPT_WRITE_SIZE_T("hpa_hugification_threshold") | ||
| 1475 | OPT_WRITE_UINT64("hpa_hugify_delay_ms") | ||
| 1476 | OPT_WRITE_UINT64("hpa_min_purge_interval_ms") | ||
| 1477 | if (je_mallctl("opt.hpa_dirty_mult", (void *)&u32v, &u32sz, NULL, 0) | ||
| 1478 | == 0) { | ||
| 1479 | /* | ||
| 1480 | * We cheat a little and "know" the secret meaning of this | ||
| 1481 | * representation. | ||
| 1482 | */ | ||
| 1483 | if (u32v == (uint32_t)-1) { | ||
| 1484 | const char *neg1 = "-1"; | ||
| 1485 | emitter_kv(emitter, "hpa_dirty_mult", | ||
| 1486 | "opt.hpa_dirty_mult", emitter_type_string, &neg1); | ||
| 1487 | } else { | ||
| 1488 | char buf[FXP_BUF_SIZE]; | ||
| 1489 | fxp_print(u32v, buf); | ||
| 1490 | const char *bufp = buf; | ||
| 1491 | emitter_kv(emitter, "hpa_dirty_mult", | ||
| 1492 | "opt.hpa_dirty_mult", emitter_type_string, &bufp); | ||
| 1493 | } | ||
| 1494 | } | ||
| 1495 | OPT_WRITE_SIZE_T("hpa_sec_nshards") | ||
| 1496 | OPT_WRITE_SIZE_T("hpa_sec_max_alloc") | ||
| 1497 | OPT_WRITE_SIZE_T("hpa_sec_max_bytes") | ||
| 1498 | OPT_WRITE_SIZE_T("hpa_sec_bytes_after_flush") | ||
| 1499 | OPT_WRITE_SIZE_T("hpa_sec_batch_fill_extra") | ||
| 1500 | OPT_WRITE_CHAR_P("metadata_thp") | ||
| 1501 | OPT_WRITE_INT64("mutex_max_spin") | ||
| 1502 | OPT_WRITE_BOOL_MUTABLE("background_thread", "background_thread") | ||
| 1503 | OPT_WRITE_SSIZE_T_MUTABLE("dirty_decay_ms", "arenas.dirty_decay_ms") | ||
| 1504 | OPT_WRITE_SSIZE_T_MUTABLE("muzzy_decay_ms", "arenas.muzzy_decay_ms") | ||
| 1505 | OPT_WRITE_SIZE_T("lg_extent_max_active_fit") | ||
| 1506 | OPT_WRITE_CHAR_P("junk") | ||
| 1507 | OPT_WRITE_BOOL("zero") | ||
| 1508 | OPT_WRITE_BOOL("utrace") | ||
| 1509 | OPT_WRITE_BOOL("xmalloc") | ||
| 1510 | OPT_WRITE_BOOL("experimental_infallible_new") | ||
| 1511 | OPT_WRITE_BOOL("tcache") | ||
| 1512 | OPT_WRITE_SIZE_T("tcache_max") | ||
| 1513 | OPT_WRITE_UNSIGNED("tcache_nslots_small_min") | ||
| 1514 | OPT_WRITE_UNSIGNED("tcache_nslots_small_max") | ||
| 1515 | OPT_WRITE_UNSIGNED("tcache_nslots_large") | ||
| 1516 | OPT_WRITE_SSIZE_T("lg_tcache_nslots_mul") | ||
| 1517 | OPT_WRITE_SIZE_T("tcache_gc_incr_bytes") | ||
| 1518 | OPT_WRITE_SIZE_T("tcache_gc_delay_bytes") | ||
| 1519 | OPT_WRITE_UNSIGNED("lg_tcache_flush_small_div") | ||
| 1520 | OPT_WRITE_UNSIGNED("lg_tcache_flush_large_div") | ||
| 1521 | OPT_WRITE_CHAR_P("thp") | ||
| 1522 | OPT_WRITE_BOOL("prof") | ||
| 1523 | OPT_WRITE_CHAR_P("prof_prefix") | ||
| 1524 | OPT_WRITE_BOOL_MUTABLE("prof_active", "prof.active") | ||
| 1525 | OPT_WRITE_BOOL_MUTABLE("prof_thread_active_init", | ||
| 1526 | "prof.thread_active_init") | ||
| 1527 | OPT_WRITE_SSIZE_T_MUTABLE("lg_prof_sample", "prof.lg_sample") | ||
| 1528 | OPT_WRITE_BOOL("prof_accum") | ||
| 1529 | OPT_WRITE_SSIZE_T("lg_prof_interval") | ||
| 1530 | OPT_WRITE_BOOL("prof_gdump") | ||
| 1531 | OPT_WRITE_BOOL("prof_final") | ||
| 1532 | OPT_WRITE_BOOL("prof_leak") | ||
| 1533 | OPT_WRITE_BOOL("prof_leak_error") | ||
| 1534 | OPT_WRITE_BOOL("stats_print") | ||
| 1535 | OPT_WRITE_CHAR_P("stats_print_opts") | ||
| 1536 | OPT_WRITE_BOOL("stats_print") | ||
| 1537 | OPT_WRITE_CHAR_P("stats_print_opts") | ||
| 1538 | OPT_WRITE_INT64("stats_interval") | ||
| 1539 | OPT_WRITE_CHAR_P("stats_interval_opts") | ||
| 1540 | OPT_WRITE_CHAR_P("zero_realloc") | ||
| 1541 | |||
| 1542 | emitter_dict_end(emitter); | ||
| 1543 | |||
| 1544 | #undef OPT_WRITE | ||
| 1545 | #undef OPT_WRITE_MUTABLE | ||
| 1546 | #undef OPT_WRITE_BOOL | ||
| 1547 | #undef OPT_WRITE_BOOL_MUTABLE | ||
| 1548 | #undef OPT_WRITE_UNSIGNED | ||
| 1549 | #undef OPT_WRITE_SSIZE_T | ||
| 1550 | #undef OPT_WRITE_SSIZE_T_MUTABLE | ||
| 1551 | #undef OPT_WRITE_CHAR_P | ||
| 1552 | |||
| 1553 | /* prof. */ | ||
| 1554 | if (config_prof) { | ||
| 1555 | emitter_dict_begin(emitter, "prof", "Profiling settings"); | ||
| 1556 | |||
| 1557 | CTL_GET("prof.thread_active_init", &bv, bool); | ||
| 1558 | emitter_kv(emitter, "thread_active_init", | ||
| 1559 | "prof.thread_active_init", emitter_type_bool, &bv); | ||
| 1560 | |||
| 1561 | CTL_GET("prof.active", &bv, bool); | ||
| 1562 | emitter_kv(emitter, "active", "prof.active", emitter_type_bool, | ||
| 1563 | &bv); | ||
| 1564 | |||
| 1565 | CTL_GET("prof.gdump", &bv, bool); | ||
| 1566 | emitter_kv(emitter, "gdump", "prof.gdump", emitter_type_bool, | ||
| 1567 | &bv); | ||
| 1568 | |||
| 1569 | CTL_GET("prof.interval", &u64v, uint64_t); | ||
| 1570 | emitter_kv(emitter, "interval", "prof.interval", | ||
| 1571 | emitter_type_uint64, &u64v); | ||
| 1572 | |||
| 1573 | CTL_GET("prof.lg_sample", &ssv, ssize_t); | ||
| 1574 | emitter_kv(emitter, "lg_sample", "prof.lg_sample", | ||
| 1575 | emitter_type_ssize, &ssv); | ||
| 1576 | |||
| 1577 | emitter_dict_end(emitter); /* Close "prof". */ | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | /* arenas. */ | ||
| 1581 | /* | ||
| 1582 | * The json output sticks arena info into an "arenas" dict; the table | ||
| 1583 | * output puts them at the top-level. | ||
| 1584 | */ | ||
| 1585 | emitter_json_object_kv_begin(emitter, "arenas"); | ||
| 1586 | |||
| 1587 | CTL_GET("arenas.narenas", &uv, unsigned); | ||
| 1588 | emitter_kv(emitter, "narenas", "Arenas", emitter_type_unsigned, &uv); | ||
| 1589 | |||
| 1590 | /* | ||
| 1591 | * Decay settings are emitted only in json mode; in table mode, they're | ||
| 1592 | * emitted as notes with the opt output, above. | ||
| 1593 | */ | ||
| 1594 | CTL_GET("arenas.dirty_decay_ms", &ssv, ssize_t); | ||
| 1595 | emitter_json_kv(emitter, "dirty_decay_ms", emitter_type_ssize, &ssv); | ||
| 1596 | |||
| 1597 | CTL_GET("arenas.muzzy_decay_ms", &ssv, ssize_t); | ||
| 1598 | emitter_json_kv(emitter, "muzzy_decay_ms", emitter_type_ssize, &ssv); | ||
| 1599 | |||
| 1600 | CTL_GET("arenas.quantum", &sv, size_t); | ||
| 1601 | emitter_kv(emitter, "quantum", "Quantum size", emitter_type_size, &sv); | ||
| 1602 | |||
| 1603 | CTL_GET("arenas.page", &sv, size_t); | ||
| 1604 | emitter_kv(emitter, "page", "Page size", emitter_type_size, &sv); | ||
| 1605 | |||
| 1606 | if (je_mallctl("arenas.tcache_max", (void *)&sv, &ssz, NULL, 0) == 0) { | ||
| 1607 | emitter_kv(emitter, "tcache_max", | ||
| 1608 | "Maximum thread-cached size class", emitter_type_size, &sv); | ||
| 1609 | } | ||
| 1610 | |||
| 1611 | unsigned arenas_nbins; | ||
| 1612 | CTL_GET("arenas.nbins", &arenas_nbins, unsigned); | ||
| 1613 | emitter_kv(emitter, "nbins", "Number of bin size classes", | ||
| 1614 | emitter_type_unsigned, &arenas_nbins); | ||
| 1615 | |||
| 1616 | unsigned arenas_nhbins; | ||
| 1617 | CTL_GET("arenas.nhbins", &arenas_nhbins, unsigned); | ||
| 1618 | emitter_kv(emitter, "nhbins", "Number of thread-cache bin size classes", | ||
| 1619 | emitter_type_unsigned, &arenas_nhbins); | ||
| 1620 | |||
| 1621 | /* | ||
| 1622 | * We do enough mallctls in a loop that we actually want to omit them | ||
| 1623 | * (not just omit the printing). | ||
| 1624 | */ | ||
| 1625 | if (emitter_outputs_json(emitter)) { | ||
| 1626 | emitter_json_array_kv_begin(emitter, "bin"); | ||
| 1627 | size_t arenas_bin_mib[CTL_MAX_DEPTH]; | ||
| 1628 | CTL_LEAF_PREPARE(arenas_bin_mib, 0, "arenas.bin"); | ||
| 1629 | for (unsigned i = 0; i < arenas_nbins; i++) { | ||
| 1630 | arenas_bin_mib[2] = i; | ||
| 1631 | emitter_json_object_begin(emitter); | ||
| 1632 | |||
| 1633 | CTL_LEAF(arenas_bin_mib, 3, "size", &sv, size_t); | ||
| 1634 | emitter_json_kv(emitter, "size", emitter_type_size, | ||
| 1635 | &sv); | ||
| 1636 | |||
| 1637 | CTL_LEAF(arenas_bin_mib, 3, "nregs", &u32v, uint32_t); | ||
| 1638 | emitter_json_kv(emitter, "nregs", emitter_type_uint32, | ||
| 1639 | &u32v); | ||
| 1640 | |||
| 1641 | CTL_LEAF(arenas_bin_mib, 3, "slab_size", &sv, size_t); | ||
| 1642 | emitter_json_kv(emitter, "slab_size", emitter_type_size, | ||
| 1643 | &sv); | ||
| 1644 | |||
| 1645 | CTL_LEAF(arenas_bin_mib, 3, "nshards", &u32v, uint32_t); | ||
| 1646 | emitter_json_kv(emitter, "nshards", emitter_type_uint32, | ||
| 1647 | &u32v); | ||
| 1648 | |||
| 1649 | emitter_json_object_end(emitter); | ||
| 1650 | } | ||
| 1651 | emitter_json_array_end(emitter); /* Close "bin". */ | ||
| 1652 | } | ||
| 1653 | |||
| 1654 | unsigned nlextents; | ||
| 1655 | CTL_GET("arenas.nlextents", &nlextents, unsigned); | ||
| 1656 | emitter_kv(emitter, "nlextents", "Number of large size classes", | ||
| 1657 | emitter_type_unsigned, &nlextents); | ||
| 1658 | |||
| 1659 | if (emitter_outputs_json(emitter)) { | ||
| 1660 | emitter_json_array_kv_begin(emitter, "lextent"); | ||
| 1661 | size_t arenas_lextent_mib[CTL_MAX_DEPTH]; | ||
| 1662 | CTL_LEAF_PREPARE(arenas_lextent_mib, 0, "arenas.lextent"); | ||
| 1663 | for (unsigned i = 0; i < nlextents; i++) { | ||
| 1664 | arenas_lextent_mib[2] = i; | ||
| 1665 | emitter_json_object_begin(emitter); | ||
| 1666 | |||
| 1667 | CTL_LEAF(arenas_lextent_mib, 3, "size", &sv, size_t); | ||
| 1668 | emitter_json_kv(emitter, "size", emitter_type_size, | ||
| 1669 | &sv); | ||
| 1670 | |||
| 1671 | emitter_json_object_end(emitter); | ||
| 1672 | } | ||
| 1673 | emitter_json_array_end(emitter); /* Close "lextent". */ | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | emitter_json_object_end(emitter); /* Close "arenas" */ | ||
| 1677 | } | ||
| 1678 | |||
| 1679 | JEMALLOC_COLD | ||
| 1680 | static void | ||
| 1681 | stats_print_helper(emitter_t *emitter, bool merged, bool destroyed, | ||
| 1682 | bool unmerged, bool bins, bool large, bool mutex, bool extents, bool hpa) { | ||
| 1683 | /* | ||
| 1684 | * These should be deleted. We keep them around for a while, to aid in | ||
| 1685 | * the transition to the emitter code. | ||
| 1686 | */ | ||
| 1687 | size_t allocated, active, metadata, metadata_thp, resident, mapped, | ||
| 1688 | retained; | ||
| 1689 | size_t num_background_threads; | ||
| 1690 | size_t zero_reallocs; | ||
| 1691 | uint64_t background_thread_num_runs, background_thread_run_interval; | ||
| 1692 | |||
| 1693 | CTL_GET("stats.allocated", &allocated, size_t); | ||
| 1694 | CTL_GET("stats.active", &active, size_t); | ||
| 1695 | CTL_GET("stats.metadata", &metadata, size_t); | ||
| 1696 | CTL_GET("stats.metadata_thp", &metadata_thp, size_t); | ||
| 1697 | CTL_GET("stats.resident", &resident, size_t); | ||
| 1698 | CTL_GET("stats.mapped", &mapped, size_t); | ||
| 1699 | CTL_GET("stats.retained", &retained, size_t); | ||
| 1700 | |||
| 1701 | CTL_GET("stats.zero_reallocs", &zero_reallocs, size_t); | ||
| 1702 | |||
| 1703 | if (have_background_thread) { | ||
| 1704 | CTL_GET("stats.background_thread.num_threads", | ||
| 1705 | &num_background_threads, size_t); | ||
| 1706 | CTL_GET("stats.background_thread.num_runs", | ||
| 1707 | &background_thread_num_runs, uint64_t); | ||
| 1708 | CTL_GET("stats.background_thread.run_interval", | ||
| 1709 | &background_thread_run_interval, uint64_t); | ||
| 1710 | } else { | ||
| 1711 | num_background_threads = 0; | ||
| 1712 | background_thread_num_runs = 0; | ||
| 1713 | background_thread_run_interval = 0; | ||
| 1714 | } | ||
| 1715 | |||
| 1716 | /* Generic global stats. */ | ||
| 1717 | emitter_json_object_kv_begin(emitter, "stats"); | ||
| 1718 | emitter_json_kv(emitter, "allocated", emitter_type_size, &allocated); | ||
| 1719 | emitter_json_kv(emitter, "active", emitter_type_size, &active); | ||
| 1720 | emitter_json_kv(emitter, "metadata", emitter_type_size, &metadata); | ||
| 1721 | emitter_json_kv(emitter, "metadata_thp", emitter_type_size, | ||
| 1722 | &metadata_thp); | ||
| 1723 | emitter_json_kv(emitter, "resident", emitter_type_size, &resident); | ||
| 1724 | emitter_json_kv(emitter, "mapped", emitter_type_size, &mapped); | ||
| 1725 | emitter_json_kv(emitter, "retained", emitter_type_size, &retained); | ||
| 1726 | emitter_json_kv(emitter, "zero_reallocs", emitter_type_size, | ||
| 1727 | &zero_reallocs); | ||
| 1728 | |||
| 1729 | emitter_table_printf(emitter, "Allocated: %zu, active: %zu, " | ||
| 1730 | "metadata: %zu (n_thp %zu), resident: %zu, mapped: %zu, " | ||
| 1731 | "retained: %zu\n", allocated, active, metadata, metadata_thp, | ||
| 1732 | resident, mapped, retained); | ||
| 1733 | |||
| 1734 | /* Strange behaviors */ | ||
| 1735 | emitter_table_printf(emitter, | ||
| 1736 | "Count of realloc(non-null-ptr, 0) calls: %zu\n", zero_reallocs); | ||
| 1737 | |||
| 1738 | /* Background thread stats. */ | ||
| 1739 | emitter_json_object_kv_begin(emitter, "background_thread"); | ||
| 1740 | emitter_json_kv(emitter, "num_threads", emitter_type_size, | ||
| 1741 | &num_background_threads); | ||
| 1742 | emitter_json_kv(emitter, "num_runs", emitter_type_uint64, | ||
| 1743 | &background_thread_num_runs); | ||
| 1744 | emitter_json_kv(emitter, "run_interval", emitter_type_uint64, | ||
| 1745 | &background_thread_run_interval); | ||
| 1746 | emitter_json_object_end(emitter); /* Close "background_thread". */ | ||
| 1747 | |||
| 1748 | emitter_table_printf(emitter, "Background threads: %zu, " | ||
| 1749 | "num_runs: %"FMTu64", run_interval: %"FMTu64" ns\n", | ||
| 1750 | num_background_threads, background_thread_num_runs, | ||
| 1751 | background_thread_run_interval); | ||
| 1752 | |||
| 1753 | if (mutex) { | ||
| 1754 | emitter_row_t row; | ||
| 1755 | emitter_col_t name; | ||
| 1756 | emitter_col_t col64[mutex_prof_num_uint64_t_counters]; | ||
| 1757 | emitter_col_t col32[mutex_prof_num_uint32_t_counters]; | ||
| 1758 | uint64_t uptime; | ||
| 1759 | |||
| 1760 | emitter_row_init(&row); | ||
| 1761 | mutex_stats_init_cols(&row, "", &name, col64, col32); | ||
| 1762 | |||
| 1763 | emitter_table_row(emitter, &row); | ||
| 1764 | emitter_json_object_kv_begin(emitter, "mutexes"); | ||
| 1765 | |||
| 1766 | CTL_M2_GET("stats.arenas.0.uptime", 0, &uptime, uint64_t); | ||
| 1767 | |||
| 1768 | size_t stats_mutexes_mib[CTL_MAX_DEPTH]; | ||
| 1769 | CTL_LEAF_PREPARE(stats_mutexes_mib, 0, "stats.mutexes"); | ||
| 1770 | for (int i = 0; i < mutex_prof_num_global_mutexes; i++) { | ||
| 1771 | mutex_stats_read_global(stats_mutexes_mib, 2, | ||
| 1772 | global_mutex_names[i], &name, col64, col32, uptime); | ||
| 1773 | emitter_json_object_kv_begin(emitter, global_mutex_names[i]); | ||
| 1774 | mutex_stats_emit(emitter, &row, col64, col32); | ||
| 1775 | emitter_json_object_end(emitter); | ||
| 1776 | } | ||
| 1777 | |||
| 1778 | emitter_json_object_end(emitter); /* Close "mutexes". */ | ||
| 1779 | } | ||
| 1780 | |||
| 1781 | emitter_json_object_end(emitter); /* Close "stats". */ | ||
| 1782 | |||
| 1783 | if (merged || destroyed || unmerged) { | ||
| 1784 | unsigned narenas; | ||
| 1785 | |||
| 1786 | emitter_json_object_kv_begin(emitter, "stats.arenas"); | ||
| 1787 | |||
| 1788 | CTL_GET("arenas.narenas", &narenas, unsigned); | ||
| 1789 | size_t mib[3]; | ||
| 1790 | size_t miblen = sizeof(mib) / sizeof(size_t); | ||
| 1791 | size_t sz; | ||
| 1792 | VARIABLE_ARRAY(bool, initialized, narenas); | ||
| 1793 | bool destroyed_initialized; | ||
| 1794 | unsigned i, j, ninitialized; | ||
| 1795 | |||
| 1796 | xmallctlnametomib("arena.0.initialized", mib, &miblen); | ||
| 1797 | for (i = ninitialized = 0; i < narenas; i++) { | ||
| 1798 | mib[1] = i; | ||
| 1799 | sz = sizeof(bool); | ||
| 1800 | xmallctlbymib(mib, miblen, &initialized[i], &sz, | ||
| 1801 | NULL, 0); | ||
| 1802 | if (initialized[i]) { | ||
| 1803 | ninitialized++; | ||
| 1804 | } | ||
| 1805 | } | ||
| 1806 | mib[1] = MALLCTL_ARENAS_DESTROYED; | ||
| 1807 | sz = sizeof(bool); | ||
| 1808 | xmallctlbymib(mib, miblen, &destroyed_initialized, &sz, | ||
| 1809 | NULL, 0); | ||
| 1810 | |||
| 1811 | /* Merged stats. */ | ||
| 1812 | if (merged && (ninitialized > 1 || !unmerged)) { | ||
| 1813 | /* Print merged arena stats. */ | ||
| 1814 | emitter_table_printf(emitter, "Merged arenas stats:\n"); | ||
| 1815 | emitter_json_object_kv_begin(emitter, "merged"); | ||
| 1816 | stats_arena_print(emitter, MALLCTL_ARENAS_ALL, bins, | ||
| 1817 | large, mutex, extents, hpa); | ||
| 1818 | emitter_json_object_end(emitter); /* Close "merged". */ | ||
| 1819 | } | ||
| 1820 | |||
| 1821 | /* Destroyed stats. */ | ||
| 1822 | if (destroyed_initialized && destroyed) { | ||
| 1823 | /* Print destroyed arena stats. */ | ||
| 1824 | emitter_table_printf(emitter, | ||
| 1825 | "Destroyed arenas stats:\n"); | ||
| 1826 | emitter_json_object_kv_begin(emitter, "destroyed"); | ||
| 1827 | stats_arena_print(emitter, MALLCTL_ARENAS_DESTROYED, | ||
| 1828 | bins, large, mutex, extents, hpa); | ||
| 1829 | emitter_json_object_end(emitter); /* Close "destroyed". */ | ||
| 1830 | } | ||
| 1831 | |||
| 1832 | /* Unmerged stats. */ | ||
| 1833 | if (unmerged) { | ||
| 1834 | for (i = j = 0; i < narenas; i++) { | ||
| 1835 | if (initialized[i]) { | ||
| 1836 | char arena_ind_str[20]; | ||
| 1837 | malloc_snprintf(arena_ind_str, | ||
| 1838 | sizeof(arena_ind_str), "%u", i); | ||
| 1839 | emitter_json_object_kv_begin(emitter, | ||
| 1840 | arena_ind_str); | ||
| 1841 | emitter_table_printf(emitter, | ||
| 1842 | "arenas[%s]:\n", arena_ind_str); | ||
| 1843 | stats_arena_print(emitter, i, bins, | ||
| 1844 | large, mutex, extents, hpa); | ||
| 1845 | /* Close "<arena-ind>". */ | ||
| 1846 | emitter_json_object_end(emitter); | ||
| 1847 | } | ||
| 1848 | } | ||
| 1849 | } | ||
| 1850 | emitter_json_object_end(emitter); /* Close "stats.arenas". */ | ||
| 1851 | } | ||
| 1852 | } | ||
| 1853 | |||
| 1854 | void | ||
| 1855 | stats_print(write_cb_t *write_cb, void *cbopaque, const char *opts) { | ||
| 1856 | int err; | ||
| 1857 | uint64_t epoch; | ||
| 1858 | size_t u64sz; | ||
| 1859 | #define OPTION(o, v, d, s) bool v = d; | ||
| 1860 | STATS_PRINT_OPTIONS | ||
| 1861 | #undef OPTION | ||
| 1862 | |||
| 1863 | /* | ||
| 1864 | * Refresh stats, in case mallctl() was called by the application. | ||
| 1865 | * | ||
| 1866 | * Check for OOM here, since refreshing the ctl cache can trigger | ||
| 1867 | * allocation. In practice, none of the subsequent mallctl()-related | ||
| 1868 | * calls in this function will cause OOM if this one succeeds. | ||
| 1869 | * */ | ||
| 1870 | epoch = 1; | ||
| 1871 | u64sz = sizeof(uint64_t); | ||
| 1872 | err = je_mallctl("epoch", (void *)&epoch, &u64sz, (void *)&epoch, | ||
| 1873 | sizeof(uint64_t)); | ||
| 1874 | if (err != 0) { | ||
| 1875 | if (err == EAGAIN) { | ||
| 1876 | malloc_write("<jemalloc>: Memory allocation failure in " | ||
| 1877 | "mallctl(\"epoch\", ...)\n"); | ||
| 1878 | return; | ||
| 1879 | } | ||
| 1880 | malloc_write("<jemalloc>: Failure in mallctl(\"epoch\", " | ||
| 1881 | "...)\n"); | ||
| 1882 | abort(); | ||
| 1883 | } | ||
| 1884 | |||
| 1885 | if (opts != NULL) { | ||
| 1886 | for (unsigned i = 0; opts[i] != '\0'; i++) { | ||
| 1887 | switch (opts[i]) { | ||
| 1888 | #define OPTION(o, v, d, s) case o: v = s; break; | ||
| 1889 | STATS_PRINT_OPTIONS | ||
| 1890 | #undef OPTION | ||
| 1891 | default:; | ||
| 1892 | } | ||
| 1893 | } | ||
| 1894 | } | ||
| 1895 | |||
| 1896 | emitter_t emitter; | ||
| 1897 | emitter_init(&emitter, | ||
| 1898 | json ? emitter_output_json_compact : emitter_output_table, | ||
| 1899 | write_cb, cbopaque); | ||
| 1900 | emitter_begin(&emitter); | ||
| 1901 | emitter_table_printf(&emitter, "___ Begin jemalloc statistics ___\n"); | ||
| 1902 | emitter_json_object_kv_begin(&emitter, "jemalloc"); | ||
| 1903 | |||
| 1904 | if (general) { | ||
| 1905 | stats_general_print(&emitter); | ||
| 1906 | } | ||
| 1907 | if (config_stats) { | ||
| 1908 | stats_print_helper(&emitter, merged, destroyed, unmerged, | ||
| 1909 | bins, large, mutex, extents, hpa); | ||
| 1910 | } | ||
| 1911 | |||
| 1912 | emitter_json_object_end(&emitter); /* Closes the "jemalloc" dict. */ | ||
| 1913 | emitter_table_printf(&emitter, "--- End jemalloc statistics ---\n"); | ||
| 1914 | emitter_end(&emitter); | ||
| 1915 | } | ||
| 1916 | |||
| 1917 | uint64_t | ||
| 1918 | stats_interval_new_event_wait(tsd_t *tsd) { | ||
| 1919 | return stats_interval_accum_batch; | ||
| 1920 | } | ||
| 1921 | |||
| 1922 | uint64_t | ||
| 1923 | stats_interval_postponed_event_wait(tsd_t *tsd) { | ||
| 1924 | return TE_MIN_START_WAIT; | ||
| 1925 | } | ||
| 1926 | |||
| 1927 | void | ||
| 1928 | stats_interval_event_handler(tsd_t *tsd, uint64_t elapsed) { | ||
| 1929 | assert(elapsed > 0 && elapsed != TE_INVALID_ELAPSED); | ||
| 1930 | if (counter_accum(tsd_tsdn(tsd), &stats_interval_accumulated, | ||
| 1931 | elapsed)) { | ||
| 1932 | je_malloc_stats_print(NULL, NULL, opt_stats_interval_opts); | ||
| 1933 | } | ||
| 1934 | } | ||
| 1935 | |||
| 1936 | bool | ||
| 1937 | stats_boot(void) { | ||
| 1938 | uint64_t stats_interval; | ||
| 1939 | if (opt_stats_interval < 0) { | ||
| 1940 | assert(opt_stats_interval == -1); | ||
| 1941 | stats_interval = 0; | ||
| 1942 | stats_interval_accum_batch = 0; | ||
| 1943 | } else{ | ||
| 1944 | /* See comments in stats.h */ | ||
| 1945 | stats_interval = (opt_stats_interval > 0) ? | ||
| 1946 | opt_stats_interval : 1; | ||
| 1947 | uint64_t batch = stats_interval >> | ||
| 1948 | STATS_INTERVAL_ACCUM_LG_BATCH_SIZE; | ||
| 1949 | if (batch > STATS_INTERVAL_ACCUM_BATCH_MAX) { | ||
| 1950 | batch = STATS_INTERVAL_ACCUM_BATCH_MAX; | ||
| 1951 | } else if (batch == 0) { | ||
| 1952 | batch = 1; | ||
| 1953 | } | ||
| 1954 | stats_interval_accum_batch = batch; | ||
| 1955 | } | ||
| 1956 | |||
| 1957 | return counter_accum_init(&stats_interval_accumulated, stats_interval); | ||
| 1958 | } | ||
| 1959 | |||
| 1960 | void | ||
| 1961 | stats_prefork(tsdn_t *tsdn) { | ||
| 1962 | counter_prefork(tsdn, &stats_interval_accumulated); | ||
| 1963 | } | ||
| 1964 | |||
| 1965 | void | ||
| 1966 | stats_postfork_parent(tsdn_t *tsdn) { | ||
| 1967 | counter_postfork_parent(tsdn, &stats_interval_accumulated); | ||
| 1968 | } | ||
| 1969 | |||
| 1970 | void | ||
| 1971 | stats_postfork_child(tsdn_t *tsdn) { | ||
| 1972 | counter_postfork_child(tsdn, &stats_interval_accumulated); | ||
| 1973 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/sz.c b/examples/redis-unstable/deps/jemalloc/src/sz.c deleted file mode 100644 index d3115dd..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/sz.c +++ /dev/null | |||
| @@ -1,114 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | #include "jemalloc/internal/sz.h" | ||
| 4 | |||
| 5 | JEMALLOC_ALIGNED(CACHELINE) | ||
| 6 | size_t sz_pind2sz_tab[SC_NPSIZES+1]; | ||
| 7 | size_t sz_large_pad; | ||
| 8 | |||
| 9 | size_t | ||
| 10 | sz_psz_quantize_floor(size_t size) { | ||
| 11 | size_t ret; | ||
| 12 | pszind_t pind; | ||
| 13 | |||
| 14 | assert(size > 0); | ||
| 15 | assert((size & PAGE_MASK) == 0); | ||
| 16 | |||
| 17 | pind = sz_psz2ind(size - sz_large_pad + 1); | ||
| 18 | if (pind == 0) { | ||
| 19 | /* | ||
| 20 | * Avoid underflow. This short-circuit would also do the right | ||
| 21 | * thing for all sizes in the range for which there are | ||
| 22 | * PAGE-spaced size classes, but it's simplest to just handle | ||
| 23 | * the one case that would cause erroneous results. | ||
| 24 | */ | ||
| 25 | return size; | ||
| 26 | } | ||
| 27 | ret = sz_pind2sz(pind - 1) + sz_large_pad; | ||
| 28 | assert(ret <= size); | ||
| 29 | return ret; | ||
| 30 | } | ||
| 31 | |||
| 32 | size_t | ||
| 33 | sz_psz_quantize_ceil(size_t size) { | ||
| 34 | size_t ret; | ||
| 35 | |||
| 36 | assert(size > 0); | ||
| 37 | assert(size - sz_large_pad <= SC_LARGE_MAXCLASS); | ||
| 38 | assert((size & PAGE_MASK) == 0); | ||
| 39 | |||
| 40 | ret = sz_psz_quantize_floor(size); | ||
| 41 | if (ret < size) { | ||
| 42 | /* | ||
| 43 | * Skip a quantization that may have an adequately large extent, | ||
| 44 | * because under-sized extents may be mixed in. This only | ||
| 45 | * happens when an unusual size is requested, i.e. for aligned | ||
| 46 | * allocation, and is just one of several places where linear | ||
| 47 | * search would potentially find sufficiently aligned available | ||
| 48 | * memory somewhere lower. | ||
| 49 | */ | ||
| 50 | ret = sz_pind2sz(sz_psz2ind(ret - sz_large_pad + 1)) + | ||
| 51 | sz_large_pad; | ||
| 52 | } | ||
| 53 | return ret; | ||
| 54 | } | ||
| 55 | |||
| 56 | static void | ||
| 57 | sz_boot_pind2sz_tab(const sc_data_t *sc_data) { | ||
| 58 | int pind = 0; | ||
| 59 | for (unsigned i = 0; i < SC_NSIZES; i++) { | ||
| 60 | const sc_t *sc = &sc_data->sc[i]; | ||
| 61 | if (sc->psz) { | ||
| 62 | sz_pind2sz_tab[pind] = (ZU(1) << sc->lg_base) | ||
| 63 | + (ZU(sc->ndelta) << sc->lg_delta); | ||
| 64 | pind++; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | for (int i = pind; i <= (int)SC_NPSIZES; i++) { | ||
| 68 | sz_pind2sz_tab[pind] = sc_data->large_maxclass + PAGE; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | JEMALLOC_ALIGNED(CACHELINE) | ||
| 73 | size_t sz_index2size_tab[SC_NSIZES]; | ||
| 74 | |||
| 75 | static void | ||
| 76 | sz_boot_index2size_tab(const sc_data_t *sc_data) { | ||
| 77 | for (unsigned i = 0; i < SC_NSIZES; i++) { | ||
| 78 | const sc_t *sc = &sc_data->sc[i]; | ||
| 79 | sz_index2size_tab[i] = (ZU(1) << sc->lg_base) | ||
| 80 | + (ZU(sc->ndelta) << (sc->lg_delta)); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /* | ||
| 85 | * To keep this table small, we divide sizes by the tiny min size, which gives | ||
| 86 | * the smallest interval for which the result can change. | ||
| 87 | */ | ||
| 88 | JEMALLOC_ALIGNED(CACHELINE) | ||
| 89 | uint8_t sz_size2index_tab[(SC_LOOKUP_MAXCLASS >> SC_LG_TINY_MIN) + 1]; | ||
| 90 | |||
| 91 | static void | ||
| 92 | sz_boot_size2index_tab(const sc_data_t *sc_data) { | ||
| 93 | size_t dst_max = (SC_LOOKUP_MAXCLASS >> SC_LG_TINY_MIN) + 1; | ||
| 94 | size_t dst_ind = 0; | ||
| 95 | for (unsigned sc_ind = 0; sc_ind < SC_NSIZES && dst_ind < dst_max; | ||
| 96 | sc_ind++) { | ||
| 97 | const sc_t *sc = &sc_data->sc[sc_ind]; | ||
| 98 | size_t sz = (ZU(1) << sc->lg_base) | ||
| 99 | + (ZU(sc->ndelta) << sc->lg_delta); | ||
| 100 | size_t max_ind = ((sz + (ZU(1) << SC_LG_TINY_MIN) - 1) | ||
| 101 | >> SC_LG_TINY_MIN); | ||
| 102 | for (; dst_ind <= max_ind && dst_ind < dst_max; dst_ind++) { | ||
| 103 | sz_size2index_tab[dst_ind] = sc_ind; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | void | ||
| 109 | sz_boot(const sc_data_t *sc_data, bool cache_oblivious) { | ||
| 110 | sz_large_pad = cache_oblivious ? PAGE : 0; | ||
| 111 | sz_boot_pind2sz_tab(sc_data); | ||
| 112 | sz_boot_index2size_tab(sc_data); | ||
| 113 | sz_boot_size2index_tab(sc_data); | ||
| 114 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/tcache.c b/examples/redis-unstable/deps/jemalloc/src/tcache.c deleted file mode 100644 index fa16732..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/tcache.c +++ /dev/null | |||
| @@ -1,1101 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/mutex.h" | ||
| 6 | #include "jemalloc/internal/safety_check.h" | ||
| 7 | #include "jemalloc/internal/san.h" | ||
| 8 | #include "jemalloc/internal/sc.h" | ||
| 9 | |||
| 10 | /******************************************************************************/ | ||
| 11 | /* Data. */ | ||
| 12 | |||
| 13 | bool opt_tcache = true; | ||
| 14 | |||
| 15 | /* tcache_maxclass is set to 32KB by default. */ | ||
| 16 | size_t opt_tcache_max = ((size_t)1) << 15; | ||
| 17 | |||
| 18 | /* Reasonable defaults for min and max values. */ | ||
| 19 | unsigned opt_tcache_nslots_small_min = 20; | ||
| 20 | unsigned opt_tcache_nslots_small_max = 200; | ||
| 21 | unsigned opt_tcache_nslots_large = 20; | ||
| 22 | |||
| 23 | /* | ||
| 24 | * We attempt to make the number of slots in a tcache bin for a given size class | ||
| 25 | * equal to the number of objects in a slab times some multiplier. By default, | ||
| 26 | * the multiplier is 2 (i.e. we set the maximum number of objects in the tcache | ||
| 27 | * to twice the number of objects in a slab). | ||
| 28 | * This is bounded by some other constraints as well, like the fact that it | ||
| 29 | * must be even, must be less than opt_tcache_nslots_small_max, etc.. | ||
| 30 | */ | ||
| 31 | ssize_t opt_lg_tcache_nslots_mul = 1; | ||
| 32 | |||
| 33 | /* | ||
| 34 | * Number of allocation bytes between tcache incremental GCs. Again, this | ||
| 35 | * default just seems to work well; more tuning is possible. | ||
| 36 | */ | ||
| 37 | size_t opt_tcache_gc_incr_bytes = 65536; | ||
| 38 | |||
| 39 | /* | ||
| 40 | * With default settings, we may end up flushing small bins frequently with | ||
| 41 | * small flush amounts. To limit this tendency, we can set a number of bytes to | ||
| 42 | * "delay" by. If we try to flush N M-byte items, we decrease that size-class's | ||
| 43 | * delay by N * M. So, if delay is 1024 and we're looking at the 64-byte size | ||
| 44 | * class, we won't do any flushing until we've been asked to flush 1024/64 == 16 | ||
| 45 | * items. This can happen in any configuration (i.e. being asked to flush 16 | ||
| 46 | * items once, or 4 items 4 times). | ||
| 47 | * | ||
| 48 | * Practically, this is stored as a count of items in a uint8_t, so the | ||
| 49 | * effective maximum value for a size class is 255 * sz. | ||
| 50 | */ | ||
| 51 | size_t opt_tcache_gc_delay_bytes = 0; | ||
| 52 | |||
| 53 | /* | ||
| 54 | * When a cache bin is flushed because it's full, how much of it do we flush? | ||
| 55 | * By default, we flush half the maximum number of items. | ||
| 56 | */ | ||
| 57 | unsigned opt_lg_tcache_flush_small_div = 1; | ||
| 58 | unsigned opt_lg_tcache_flush_large_div = 1; | ||
| 59 | |||
| 60 | cache_bin_info_t *tcache_bin_info; | ||
| 61 | |||
| 62 | /* Total stack size required (per tcache). Include the padding above. */ | ||
| 63 | static size_t tcache_bin_alloc_size; | ||
| 64 | static size_t tcache_bin_alloc_alignment; | ||
| 65 | |||
| 66 | /* Number of cache bins enabled, including both large and small. */ | ||
| 67 | unsigned nhbins; | ||
| 68 | /* Max size class to be cached (can be small or large). */ | ||
| 69 | size_t tcache_maxclass; | ||
| 70 | |||
| 71 | tcaches_t *tcaches; | ||
| 72 | |||
| 73 | /* Index of first element within tcaches that has never been used. */ | ||
| 74 | static unsigned tcaches_past; | ||
| 75 | |||
| 76 | /* Head of singly linked list tracking available tcaches elements. */ | ||
| 77 | static tcaches_t *tcaches_avail; | ||
| 78 | |||
| 79 | /* Protects tcaches{,_past,_avail}. */ | ||
| 80 | static malloc_mutex_t tcaches_mtx; | ||
| 81 | |||
| 82 | /******************************************************************************/ | ||
| 83 | |||
| 84 | size_t | ||
| 85 | tcache_salloc(tsdn_t *tsdn, const void *ptr) { | ||
| 86 | return arena_salloc(tsdn, ptr); | ||
| 87 | } | ||
| 88 | |||
| 89 | uint64_t | ||
| 90 | tcache_gc_new_event_wait(tsd_t *tsd) { | ||
| 91 | return opt_tcache_gc_incr_bytes; | ||
| 92 | } | ||
| 93 | |||
| 94 | uint64_t | ||
| 95 | tcache_gc_postponed_event_wait(tsd_t *tsd) { | ||
| 96 | return TE_MIN_START_WAIT; | ||
| 97 | } | ||
| 98 | |||
| 99 | uint64_t | ||
| 100 | tcache_gc_dalloc_new_event_wait(tsd_t *tsd) { | ||
| 101 | return opt_tcache_gc_incr_bytes; | ||
| 102 | } | ||
| 103 | |||
| 104 | uint64_t | ||
| 105 | tcache_gc_dalloc_postponed_event_wait(tsd_t *tsd) { | ||
| 106 | return TE_MIN_START_WAIT; | ||
| 107 | } | ||
| 108 | |||
| 109 | static uint8_t | ||
| 110 | tcache_gc_item_delay_compute(szind_t szind) { | ||
| 111 | assert(szind < SC_NBINS); | ||
| 112 | size_t sz = sz_index2size(szind); | ||
| 113 | size_t item_delay = opt_tcache_gc_delay_bytes / sz; | ||
| 114 | size_t delay_max = ZU(1) | ||
| 115 | << (sizeof(((tcache_slow_t *)NULL)->bin_flush_delay_items[0]) * 8); | ||
| 116 | if (item_delay >= delay_max) { | ||
| 117 | item_delay = delay_max - 1; | ||
| 118 | } | ||
| 119 | return (uint8_t)item_delay; | ||
| 120 | } | ||
| 121 | |||
| 122 | static void | ||
| 123 | tcache_gc_small(tsd_t *tsd, tcache_slow_t *tcache_slow, tcache_t *tcache, | ||
| 124 | szind_t szind) { | ||
| 125 | /* Aim to flush 3/4 of items below low-water. */ | ||
| 126 | assert(szind < SC_NBINS); | ||
| 127 | |||
| 128 | cache_bin_t *cache_bin = &tcache->bins[szind]; | ||
| 129 | cache_bin_sz_t ncached = cache_bin_ncached_get_local(cache_bin, | ||
| 130 | &tcache_bin_info[szind]); | ||
| 131 | cache_bin_sz_t low_water = cache_bin_low_water_get(cache_bin, | ||
| 132 | &tcache_bin_info[szind]); | ||
| 133 | assert(!tcache_slow->bin_refilled[szind]); | ||
| 134 | |||
| 135 | size_t nflush = low_water - (low_water >> 2); | ||
| 136 | if (nflush < tcache_slow->bin_flush_delay_items[szind]) { | ||
| 137 | /* Workaround for a conversion warning. */ | ||
| 138 | uint8_t nflush_uint8 = (uint8_t)nflush; | ||
| 139 | assert(sizeof(tcache_slow->bin_flush_delay_items[0]) == | ||
| 140 | sizeof(nflush_uint8)); | ||
| 141 | tcache_slow->bin_flush_delay_items[szind] -= nflush_uint8; | ||
| 142 | return; | ||
| 143 | } else { | ||
| 144 | tcache_slow->bin_flush_delay_items[szind] | ||
| 145 | = tcache_gc_item_delay_compute(szind); | ||
| 146 | } | ||
| 147 | |||
| 148 | tcache_bin_flush_small(tsd, tcache, cache_bin, szind, | ||
| 149 | (unsigned)(ncached - nflush)); | ||
| 150 | |||
| 151 | /* | ||
| 152 | * Reduce fill count by 2X. Limit lg_fill_div such that | ||
| 153 | * the fill count is always at least 1. | ||
| 154 | */ | ||
| 155 | if ((cache_bin_info_ncached_max(&tcache_bin_info[szind]) | ||
| 156 | >> (tcache_slow->lg_fill_div[szind] + 1)) >= 1) { | ||
| 157 | tcache_slow->lg_fill_div[szind]++; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | static void | ||
| 162 | tcache_gc_large(tsd_t *tsd, tcache_slow_t *tcache_slow, tcache_t *tcache, | ||
| 163 | szind_t szind) { | ||
| 164 | /* Like the small GC; flush 3/4 of untouched items. */ | ||
| 165 | assert(szind >= SC_NBINS); | ||
| 166 | cache_bin_t *cache_bin = &tcache->bins[szind]; | ||
| 167 | cache_bin_sz_t ncached = cache_bin_ncached_get_local(cache_bin, | ||
| 168 | &tcache_bin_info[szind]); | ||
| 169 | cache_bin_sz_t low_water = cache_bin_low_water_get(cache_bin, | ||
| 170 | &tcache_bin_info[szind]); | ||
| 171 | tcache_bin_flush_large(tsd, tcache, cache_bin, szind, | ||
| 172 | (unsigned)(ncached - low_water + (low_water >> 2))); | ||
| 173 | } | ||
| 174 | |||
| 175 | static void | ||
| 176 | tcache_event(tsd_t *tsd) { | ||
| 177 | tcache_t *tcache = tcache_get(tsd); | ||
| 178 | if (tcache == NULL) { | ||
| 179 | return; | ||
| 180 | } | ||
| 181 | |||
| 182 | tcache_slow_t *tcache_slow = tsd_tcache_slowp_get(tsd); | ||
| 183 | szind_t szind = tcache_slow->next_gc_bin; | ||
| 184 | bool is_small = (szind < SC_NBINS); | ||
| 185 | cache_bin_t *cache_bin = &tcache->bins[szind]; | ||
| 186 | |||
| 187 | tcache_bin_flush_stashed(tsd, tcache, cache_bin, szind, is_small); | ||
| 188 | |||
| 189 | cache_bin_sz_t low_water = cache_bin_low_water_get(cache_bin, | ||
| 190 | &tcache_bin_info[szind]); | ||
| 191 | if (low_water > 0) { | ||
| 192 | if (is_small) { | ||
| 193 | tcache_gc_small(tsd, tcache_slow, tcache, szind); | ||
| 194 | } else { | ||
| 195 | tcache_gc_large(tsd, tcache_slow, tcache, szind); | ||
| 196 | } | ||
| 197 | } else if (is_small && tcache_slow->bin_refilled[szind]) { | ||
| 198 | assert(low_water == 0); | ||
| 199 | /* | ||
| 200 | * Increase fill count by 2X for small bins. Make sure | ||
| 201 | * lg_fill_div stays greater than 0. | ||
| 202 | */ | ||
| 203 | if (tcache_slow->lg_fill_div[szind] > 1) { | ||
| 204 | tcache_slow->lg_fill_div[szind]--; | ||
| 205 | } | ||
| 206 | tcache_slow->bin_refilled[szind] = false; | ||
| 207 | } | ||
| 208 | cache_bin_low_water_set(cache_bin); | ||
| 209 | |||
| 210 | tcache_slow->next_gc_bin++; | ||
| 211 | if (tcache_slow->next_gc_bin == nhbins) { | ||
| 212 | tcache_slow->next_gc_bin = 0; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | void | ||
| 217 | tcache_gc_event_handler(tsd_t *tsd, uint64_t elapsed) { | ||
| 218 | assert(elapsed == TE_INVALID_ELAPSED); | ||
| 219 | tcache_event(tsd); | ||
| 220 | } | ||
| 221 | |||
| 222 | void | ||
| 223 | tcache_gc_dalloc_event_handler(tsd_t *tsd, uint64_t elapsed) { | ||
| 224 | assert(elapsed == TE_INVALID_ELAPSED); | ||
| 225 | tcache_event(tsd); | ||
| 226 | } | ||
| 227 | |||
| 228 | void * | ||
| 229 | tcache_alloc_small_hard(tsdn_t *tsdn, arena_t *arena, | ||
| 230 | tcache_t *tcache, cache_bin_t *cache_bin, szind_t binind, | ||
| 231 | bool *tcache_success) { | ||
| 232 | tcache_slow_t *tcache_slow = tcache->tcache_slow; | ||
| 233 | void *ret; | ||
| 234 | |||
| 235 | assert(tcache_slow->arena != NULL); | ||
| 236 | unsigned nfill = cache_bin_info_ncached_max(&tcache_bin_info[binind]) | ||
| 237 | >> tcache_slow->lg_fill_div[binind]; | ||
| 238 | arena_cache_bin_fill_small(tsdn, arena, cache_bin, | ||
| 239 | &tcache_bin_info[binind], binind, nfill); | ||
| 240 | tcache_slow->bin_refilled[binind] = true; | ||
| 241 | ret = cache_bin_alloc(cache_bin, tcache_success); | ||
| 242 | |||
| 243 | return ret; | ||
| 244 | } | ||
| 245 | |||
| 246 | static const void * | ||
| 247 | tcache_bin_flush_ptr_getter(void *arr_ctx, size_t ind) { | ||
| 248 | cache_bin_ptr_array_t *arr = (cache_bin_ptr_array_t *)arr_ctx; | ||
| 249 | return arr->ptr[ind]; | ||
| 250 | } | ||
| 251 | |||
| 252 | static void | ||
| 253 | tcache_bin_flush_metadata_visitor(void *szind_sum_ctx, | ||
| 254 | emap_full_alloc_ctx_t *alloc_ctx) { | ||
| 255 | size_t *szind_sum = (size_t *)szind_sum_ctx; | ||
| 256 | *szind_sum -= alloc_ctx->szind; | ||
| 257 | util_prefetch_write_range(alloc_ctx->edata, sizeof(edata_t)); | ||
| 258 | } | ||
| 259 | |||
| 260 | JEMALLOC_NOINLINE static void | ||
| 261 | tcache_bin_flush_size_check_fail(cache_bin_ptr_array_t *arr, szind_t szind, | ||
| 262 | size_t nptrs, emap_batch_lookup_result_t *edatas) { | ||
| 263 | bool found_mismatch = false; | ||
| 264 | for (size_t i = 0; i < nptrs; i++) { | ||
| 265 | szind_t true_szind = edata_szind_get(edatas[i].edata); | ||
| 266 | if (true_szind != szind) { | ||
| 267 | found_mismatch = true; | ||
| 268 | safety_check_fail_sized_dealloc( | ||
| 269 | /* current_dealloc */ false, | ||
| 270 | /* ptr */ tcache_bin_flush_ptr_getter(arr, i), | ||
| 271 | /* true_size */ sz_index2size(true_szind), | ||
| 272 | /* input_size */ sz_index2size(szind)); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | assert(found_mismatch); | ||
| 276 | } | ||
| 277 | |||
| 278 | static void | ||
| 279 | tcache_bin_flush_edatas_lookup(tsd_t *tsd, cache_bin_ptr_array_t *arr, | ||
| 280 | szind_t binind, size_t nflush, emap_batch_lookup_result_t *edatas) { | ||
| 281 | |||
| 282 | /* | ||
| 283 | * This gets compiled away when config_opt_safety_checks is false. | ||
| 284 | * Checks for sized deallocation bugs, failing early rather than | ||
| 285 | * corrupting metadata. | ||
| 286 | */ | ||
| 287 | size_t szind_sum = binind * nflush; | ||
| 288 | emap_edata_lookup_batch(tsd, &arena_emap_global, nflush, | ||
| 289 | &tcache_bin_flush_ptr_getter, (void *)arr, | ||
| 290 | &tcache_bin_flush_metadata_visitor, (void *)&szind_sum, | ||
| 291 | edatas); | ||
| 292 | if (config_opt_safety_checks && unlikely(szind_sum != 0)) { | ||
| 293 | tcache_bin_flush_size_check_fail(arr, binind, nflush, edatas); | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | JEMALLOC_ALWAYS_INLINE bool | ||
| 298 | tcache_bin_flush_match(edata_t *edata, unsigned cur_arena_ind, | ||
| 299 | unsigned cur_binshard, bool small) { | ||
| 300 | if (small) { | ||
| 301 | return edata_arena_ind_get(edata) == cur_arena_ind | ||
| 302 | && edata_binshard_get(edata) == cur_binshard; | ||
| 303 | } else { | ||
| 304 | return edata_arena_ind_get(edata) == cur_arena_ind; | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | JEMALLOC_ALWAYS_INLINE void | ||
| 309 | tcache_bin_flush_impl(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, | ||
| 310 | szind_t binind, cache_bin_ptr_array_t *ptrs, unsigned nflush, bool small) { | ||
| 311 | tcache_slow_t *tcache_slow = tcache->tcache_slow; | ||
| 312 | /* | ||
| 313 | * A couple lookup calls take tsdn; declare it once for convenience | ||
| 314 | * instead of calling tsd_tsdn(tsd) all the time. | ||
| 315 | */ | ||
| 316 | tsdn_t *tsdn = tsd_tsdn(tsd); | ||
| 317 | |||
| 318 | if (small) { | ||
| 319 | assert(binind < SC_NBINS); | ||
| 320 | } else { | ||
| 321 | assert(binind < nhbins); | ||
| 322 | } | ||
| 323 | arena_t *tcache_arena = tcache_slow->arena; | ||
| 324 | assert(tcache_arena != NULL); | ||
| 325 | |||
| 326 | /* | ||
| 327 | * Variable length array must have > 0 length; the last element is never | ||
| 328 | * touched (it's just included to satisfy the no-zero-length rule). | ||
| 329 | */ | ||
| 330 | VARIABLE_ARRAY(emap_batch_lookup_result_t, item_edata, nflush + 1); | ||
| 331 | tcache_bin_flush_edatas_lookup(tsd, ptrs, binind, nflush, item_edata); | ||
| 332 | |||
| 333 | /* | ||
| 334 | * The slabs where we freed the last remaining object in the slab (and | ||
| 335 | * so need to free the slab itself). | ||
| 336 | * Used only if small == true. | ||
| 337 | */ | ||
| 338 | unsigned dalloc_count = 0; | ||
| 339 | VARIABLE_ARRAY(edata_t *, dalloc_slabs, nflush + 1); | ||
| 340 | |||
| 341 | /* | ||
| 342 | * We're about to grab a bunch of locks. If one of them happens to be | ||
| 343 | * the one guarding the arena-level stats counters we flush our | ||
| 344 | * thread-local ones to, we do so under one critical section. | ||
| 345 | */ | ||
| 346 | bool merged_stats = false; | ||
| 347 | while (nflush > 0) { | ||
| 348 | /* Lock the arena, or bin, associated with the first object. */ | ||
| 349 | edata_t *edata = item_edata[0].edata; | ||
| 350 | unsigned cur_arena_ind = edata_arena_ind_get(edata); | ||
| 351 | arena_t *cur_arena = arena_get(tsdn, cur_arena_ind, false); | ||
| 352 | |||
| 353 | /* | ||
| 354 | * These assignments are always overwritten when small is true, | ||
| 355 | * and their values are always ignored when small is false, but | ||
| 356 | * to avoid the technical UB when we pass them as parameters, we | ||
| 357 | * need to intialize them. | ||
| 358 | */ | ||
| 359 | unsigned cur_binshard = 0; | ||
| 360 | bin_t *cur_bin = NULL; | ||
| 361 | if (small) { | ||
| 362 | cur_binshard = edata_binshard_get(edata); | ||
| 363 | cur_bin = arena_get_bin(cur_arena, binind, | ||
| 364 | cur_binshard); | ||
| 365 | assert(cur_binshard < bin_infos[binind].n_shards); | ||
| 366 | /* | ||
| 367 | * If you're looking at profiles, you might think this | ||
| 368 | * is a good place to prefetch the bin stats, which are | ||
| 369 | * often a cache miss. This turns out not to be | ||
| 370 | * helpful on the workloads we've looked at, with moving | ||
| 371 | * the bin stats next to the lock seeming to do better. | ||
| 372 | */ | ||
| 373 | } | ||
| 374 | |||
| 375 | if (small) { | ||
| 376 | malloc_mutex_lock(tsdn, &cur_bin->lock); | ||
| 377 | } | ||
| 378 | if (!small && !arena_is_auto(cur_arena)) { | ||
| 379 | malloc_mutex_lock(tsdn, &cur_arena->large_mtx); | ||
| 380 | } | ||
| 381 | |||
| 382 | /* | ||
| 383 | * If we acquired the right lock and have some stats to flush, | ||
| 384 | * flush them. | ||
| 385 | */ | ||
| 386 | if (config_stats && tcache_arena == cur_arena | ||
| 387 | && !merged_stats) { | ||
| 388 | merged_stats = true; | ||
| 389 | if (small) { | ||
| 390 | cur_bin->stats.nflushes++; | ||
| 391 | cur_bin->stats.nrequests += | ||
| 392 | cache_bin->tstats.nrequests; | ||
| 393 | cache_bin->tstats.nrequests = 0; | ||
| 394 | } else { | ||
| 395 | arena_stats_large_flush_nrequests_add(tsdn, | ||
| 396 | &tcache_arena->stats, binind, | ||
| 397 | cache_bin->tstats.nrequests); | ||
| 398 | cache_bin->tstats.nrequests = 0; | ||
| 399 | } | ||
| 400 | } | ||
| 401 | |||
| 402 | /* | ||
| 403 | * Large allocations need special prep done. Afterwards, we can | ||
| 404 | * drop the large lock. | ||
| 405 | */ | ||
| 406 | if (!small) { | ||
| 407 | for (unsigned i = 0; i < nflush; i++) { | ||
| 408 | void *ptr = ptrs->ptr[i]; | ||
| 409 | edata = item_edata[i].edata; | ||
| 410 | assert(ptr != NULL && edata != NULL); | ||
| 411 | |||
| 412 | if (tcache_bin_flush_match(edata, cur_arena_ind, | ||
| 413 | cur_binshard, small)) { | ||
| 414 | large_dalloc_prep_locked(tsdn, | ||
| 415 | edata); | ||
| 416 | } | ||
| 417 | } | ||
| 418 | } | ||
| 419 | if (!small && !arena_is_auto(cur_arena)) { | ||
| 420 | malloc_mutex_unlock(tsdn, &cur_arena->large_mtx); | ||
| 421 | } | ||
| 422 | |||
| 423 | /* Deallocate whatever we can. */ | ||
| 424 | unsigned ndeferred = 0; | ||
| 425 | /* Init only to avoid used-uninitialized warning. */ | ||
| 426 | arena_dalloc_bin_locked_info_t dalloc_bin_info = {0}; | ||
| 427 | if (small) { | ||
| 428 | arena_dalloc_bin_locked_begin(&dalloc_bin_info, binind); | ||
| 429 | } | ||
| 430 | for (unsigned i = 0; i < nflush; i++) { | ||
| 431 | void *ptr = ptrs->ptr[i]; | ||
| 432 | edata = item_edata[i].edata; | ||
| 433 | assert(ptr != NULL && edata != NULL); | ||
| 434 | if (!tcache_bin_flush_match(edata, cur_arena_ind, | ||
| 435 | cur_binshard, small)) { | ||
| 436 | /* | ||
| 437 | * The object was allocated either via a | ||
| 438 | * different arena, or a different bin in this | ||
| 439 | * arena. Either way, stash the object so that | ||
| 440 | * it can be handled in a future pass. | ||
| 441 | */ | ||
| 442 | ptrs->ptr[ndeferred] = ptr; | ||
| 443 | item_edata[ndeferred].edata = edata; | ||
| 444 | ndeferred++; | ||
| 445 | continue; | ||
| 446 | } | ||
| 447 | if (small) { | ||
| 448 | if (arena_dalloc_bin_locked_step(tsdn, | ||
| 449 | cur_arena, cur_bin, &dalloc_bin_info, | ||
| 450 | binind, edata, ptr)) { | ||
| 451 | dalloc_slabs[dalloc_count] = edata; | ||
| 452 | dalloc_count++; | ||
| 453 | } | ||
| 454 | } else { | ||
| 455 | if (large_dalloc_safety_checks(edata, ptr, | ||
| 456 | binind)) { | ||
| 457 | /* See the comment in isfree. */ | ||
| 458 | continue; | ||
| 459 | } | ||
| 460 | large_dalloc_finish(tsdn, edata); | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | if (small) { | ||
| 465 | arena_dalloc_bin_locked_finish(tsdn, cur_arena, cur_bin, | ||
| 466 | &dalloc_bin_info); | ||
| 467 | malloc_mutex_unlock(tsdn, &cur_bin->lock); | ||
| 468 | } | ||
| 469 | arena_decay_ticks(tsdn, cur_arena, nflush - ndeferred); | ||
| 470 | nflush = ndeferred; | ||
| 471 | } | ||
| 472 | |||
| 473 | /* Handle all deferred slab dalloc. */ | ||
| 474 | assert(small || dalloc_count == 0); | ||
| 475 | for (unsigned i = 0; i < dalloc_count; i++) { | ||
| 476 | edata_t *slab = dalloc_slabs[i]; | ||
| 477 | arena_slab_dalloc(tsdn, arena_get_from_edata(slab), slab); | ||
| 478 | |||
| 479 | } | ||
| 480 | |||
| 481 | if (config_stats && !merged_stats) { | ||
| 482 | if (small) { | ||
| 483 | /* | ||
| 484 | * The flush loop didn't happen to flush to this | ||
| 485 | * thread's arena, so the stats didn't get merged. | ||
| 486 | * Manually do so now. | ||
| 487 | */ | ||
| 488 | bin_t *bin = arena_bin_choose(tsdn, tcache_arena, | ||
| 489 | binind, NULL); | ||
| 490 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 491 | bin->stats.nflushes++; | ||
| 492 | bin->stats.nrequests += cache_bin->tstats.nrequests; | ||
| 493 | cache_bin->tstats.nrequests = 0; | ||
| 494 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 495 | } else { | ||
| 496 | arena_stats_large_flush_nrequests_add(tsdn, | ||
| 497 | &tcache_arena->stats, binind, | ||
| 498 | cache_bin->tstats.nrequests); | ||
| 499 | cache_bin->tstats.nrequests = 0; | ||
| 500 | } | ||
| 501 | } | ||
| 502 | |||
| 503 | } | ||
| 504 | |||
| 505 | JEMALLOC_ALWAYS_INLINE void | ||
| 506 | tcache_bin_flush_bottom(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, | ||
| 507 | szind_t binind, unsigned rem, bool small) { | ||
| 508 | tcache_bin_flush_stashed(tsd, tcache, cache_bin, binind, small); | ||
| 509 | |||
| 510 | cache_bin_sz_t ncached = cache_bin_ncached_get_local(cache_bin, | ||
| 511 | &tcache_bin_info[binind]); | ||
| 512 | assert((cache_bin_sz_t)rem <= ncached); | ||
| 513 | unsigned nflush = ncached - rem; | ||
| 514 | |||
| 515 | CACHE_BIN_PTR_ARRAY_DECLARE(ptrs, nflush); | ||
| 516 | cache_bin_init_ptr_array_for_flush(cache_bin, &tcache_bin_info[binind], | ||
| 517 | &ptrs, nflush); | ||
| 518 | |||
| 519 | tcache_bin_flush_impl(tsd, tcache, cache_bin, binind, &ptrs, nflush, | ||
| 520 | small); | ||
| 521 | |||
| 522 | cache_bin_finish_flush(cache_bin, &tcache_bin_info[binind], &ptrs, | ||
| 523 | ncached - rem); | ||
| 524 | } | ||
| 525 | |||
| 526 | void | ||
| 527 | tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, | ||
| 528 | szind_t binind, unsigned rem) { | ||
| 529 | tcache_bin_flush_bottom(tsd, tcache, cache_bin, binind, rem, true); | ||
| 530 | } | ||
| 531 | |||
| 532 | void | ||
| 533 | tcache_bin_flush_large(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, | ||
| 534 | szind_t binind, unsigned rem) { | ||
| 535 | tcache_bin_flush_bottom(tsd, tcache, cache_bin, binind, rem, false); | ||
| 536 | } | ||
| 537 | |||
| 538 | /* | ||
| 539 | * Flushing stashed happens when 1) tcache fill, 2) tcache flush, or 3) tcache | ||
| 540 | * GC event. This makes sure that the stashed items do not hold memory for too | ||
| 541 | * long, and new buffers can only be allocated when nothing is stashed. | ||
| 542 | * | ||
| 543 | * The downside is, the time between stash and flush may be relatively short, | ||
| 544 | * especially when the request rate is high. It lowers the chance of detecting | ||
| 545 | * write-after-free -- however that is a delayed detection anyway, and is less | ||
| 546 | * of a focus than the memory overhead. | ||
| 547 | */ | ||
| 548 | void | ||
| 549 | tcache_bin_flush_stashed(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin, | ||
| 550 | szind_t binind, bool is_small) { | ||
| 551 | cache_bin_info_t *info = &tcache_bin_info[binind]; | ||
| 552 | /* | ||
| 553 | * The two below are for assertion only. The content of original cached | ||
| 554 | * items remain unchanged -- the stashed items reside on the other end | ||
| 555 | * of the stack. Checking the stack head and ncached to verify. | ||
| 556 | */ | ||
| 557 | void *head_content = *cache_bin->stack_head; | ||
| 558 | cache_bin_sz_t orig_cached = cache_bin_ncached_get_local(cache_bin, | ||
| 559 | info); | ||
| 560 | |||
| 561 | cache_bin_sz_t nstashed = cache_bin_nstashed_get_local(cache_bin, info); | ||
| 562 | assert(orig_cached + nstashed <= cache_bin_info_ncached_max(info)); | ||
| 563 | if (nstashed == 0) { | ||
| 564 | return; | ||
| 565 | } | ||
| 566 | |||
| 567 | CACHE_BIN_PTR_ARRAY_DECLARE(ptrs, nstashed); | ||
| 568 | cache_bin_init_ptr_array_for_stashed(cache_bin, binind, info, &ptrs, | ||
| 569 | nstashed); | ||
| 570 | san_check_stashed_ptrs(ptrs.ptr, nstashed, sz_index2size(binind)); | ||
| 571 | tcache_bin_flush_impl(tsd, tcache, cache_bin, binind, &ptrs, nstashed, | ||
| 572 | is_small); | ||
| 573 | cache_bin_finish_flush_stashed(cache_bin, info); | ||
| 574 | |||
| 575 | assert(cache_bin_nstashed_get_local(cache_bin, info) == 0); | ||
| 576 | assert(cache_bin_ncached_get_local(cache_bin, info) == orig_cached); | ||
| 577 | assert(head_content == *cache_bin->stack_head); | ||
| 578 | } | ||
| 579 | |||
| 580 | void | ||
| 581 | tcache_arena_associate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, | ||
| 582 | tcache_t *tcache, arena_t *arena) { | ||
| 583 | assert(tcache_slow->arena == NULL); | ||
| 584 | tcache_slow->arena = arena; | ||
| 585 | |||
| 586 | if (config_stats) { | ||
| 587 | /* Link into list of extant tcaches. */ | ||
| 588 | malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); | ||
| 589 | |||
| 590 | ql_elm_new(tcache_slow, link); | ||
| 591 | ql_tail_insert(&arena->tcache_ql, tcache_slow, link); | ||
| 592 | cache_bin_array_descriptor_init( | ||
| 593 | &tcache_slow->cache_bin_array_descriptor, tcache->bins); | ||
| 594 | ql_tail_insert(&arena->cache_bin_array_descriptor_ql, | ||
| 595 | &tcache_slow->cache_bin_array_descriptor, link); | ||
| 596 | |||
| 597 | malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); | ||
| 598 | } | ||
| 599 | } | ||
| 600 | |||
| 601 | static void | ||
| 602 | tcache_arena_dissociate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, | ||
| 603 | tcache_t *tcache) { | ||
| 604 | arena_t *arena = tcache_slow->arena; | ||
| 605 | assert(arena != NULL); | ||
| 606 | if (config_stats) { | ||
| 607 | /* Unlink from list of extant tcaches. */ | ||
| 608 | malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx); | ||
| 609 | if (config_debug) { | ||
| 610 | bool in_ql = false; | ||
| 611 | tcache_slow_t *iter; | ||
| 612 | ql_foreach(iter, &arena->tcache_ql, link) { | ||
| 613 | if (iter == tcache_slow) { | ||
| 614 | in_ql = true; | ||
| 615 | break; | ||
| 616 | } | ||
| 617 | } | ||
| 618 | assert(in_ql); | ||
| 619 | } | ||
| 620 | ql_remove(&arena->tcache_ql, tcache_slow, link); | ||
| 621 | ql_remove(&arena->cache_bin_array_descriptor_ql, | ||
| 622 | &tcache_slow->cache_bin_array_descriptor, link); | ||
| 623 | tcache_stats_merge(tsdn, tcache_slow->tcache, arena); | ||
| 624 | malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx); | ||
| 625 | } | ||
| 626 | tcache_slow->arena = NULL; | ||
| 627 | } | ||
| 628 | |||
| 629 | void | ||
| 630 | tcache_arena_reassociate(tsdn_t *tsdn, tcache_slow_t *tcache_slow, | ||
| 631 | tcache_t *tcache, arena_t *arena) { | ||
| 632 | tcache_arena_dissociate(tsdn, tcache_slow, tcache); | ||
| 633 | tcache_arena_associate(tsdn, tcache_slow, tcache, arena); | ||
| 634 | } | ||
| 635 | |||
| 636 | bool | ||
| 637 | tsd_tcache_enabled_data_init(tsd_t *tsd) { | ||
| 638 | /* Called upon tsd initialization. */ | ||
| 639 | tsd_tcache_enabled_set(tsd, opt_tcache); | ||
| 640 | tsd_slow_update(tsd); | ||
| 641 | |||
| 642 | if (opt_tcache) { | ||
| 643 | /* Trigger tcache init. */ | ||
| 644 | tsd_tcache_data_init(tsd); | ||
| 645 | } | ||
| 646 | |||
| 647 | return false; | ||
| 648 | } | ||
| 649 | |||
| 650 | static void | ||
| 651 | tcache_init(tsd_t *tsd, tcache_slow_t *tcache_slow, tcache_t *tcache, | ||
| 652 | void *mem) { | ||
| 653 | tcache->tcache_slow = tcache_slow; | ||
| 654 | tcache_slow->tcache = tcache; | ||
| 655 | |||
| 656 | memset(&tcache_slow->link, 0, sizeof(ql_elm(tcache_t))); | ||
| 657 | tcache_slow->next_gc_bin = 0; | ||
| 658 | tcache_slow->arena = NULL; | ||
| 659 | tcache_slow->dyn_alloc = mem; | ||
| 660 | |||
| 661 | /* | ||
| 662 | * We reserve cache bins for all small size classes, even if some may | ||
| 663 | * not get used (i.e. bins higher than nhbins). This allows the fast | ||
| 664 | * and common paths to access cache bin metadata safely w/o worrying | ||
| 665 | * about which ones are disabled. | ||
| 666 | */ | ||
| 667 | unsigned n_reserved_bins = nhbins < SC_NBINS ? SC_NBINS : nhbins; | ||
| 668 | memset(tcache->bins, 0, sizeof(cache_bin_t) * n_reserved_bins); | ||
| 669 | |||
| 670 | size_t cur_offset = 0; | ||
| 671 | cache_bin_preincrement(tcache_bin_info, nhbins, mem, | ||
| 672 | &cur_offset); | ||
| 673 | for (unsigned i = 0; i < nhbins; i++) { | ||
| 674 | if (i < SC_NBINS) { | ||
| 675 | tcache_slow->lg_fill_div[i] = 1; | ||
| 676 | tcache_slow->bin_refilled[i] = false; | ||
| 677 | tcache_slow->bin_flush_delay_items[i] | ||
| 678 | = tcache_gc_item_delay_compute(i); | ||
| 679 | } | ||
| 680 | cache_bin_t *cache_bin = &tcache->bins[i]; | ||
| 681 | cache_bin_init(cache_bin, &tcache_bin_info[i], mem, | ||
| 682 | &cur_offset); | ||
| 683 | } | ||
| 684 | /* | ||
| 685 | * For small size classes beyond tcache_maxclass (i.e. nhbins < NBINS), | ||
| 686 | * their cache bins are initialized to a state to safely and efficiently | ||
| 687 | * fail all fastpath alloc / free, so that no additional check around | ||
| 688 | * nhbins is needed on fastpath. | ||
| 689 | */ | ||
| 690 | for (unsigned i = nhbins; i < SC_NBINS; i++) { | ||
| 691 | /* Disabled small bins. */ | ||
| 692 | cache_bin_t *cache_bin = &tcache->bins[i]; | ||
| 693 | void *fake_stack = mem; | ||
| 694 | size_t fake_offset = 0; | ||
| 695 | |||
| 696 | cache_bin_init(cache_bin, &tcache_bin_info[i], fake_stack, | ||
| 697 | &fake_offset); | ||
| 698 | assert(tcache_small_bin_disabled(i, cache_bin)); | ||
| 699 | } | ||
| 700 | |||
| 701 | cache_bin_postincrement(tcache_bin_info, nhbins, mem, | ||
| 702 | &cur_offset); | ||
| 703 | /* Sanity check that the whole stack is used. */ | ||
| 704 | assert(cur_offset == tcache_bin_alloc_size); | ||
| 705 | } | ||
| 706 | |||
| 707 | /* Initialize auto tcache (embedded in TSD). */ | ||
| 708 | bool | ||
| 709 | tsd_tcache_data_init(tsd_t *tsd) { | ||
| 710 | tcache_slow_t *tcache_slow = tsd_tcache_slowp_get_unsafe(tsd); | ||
| 711 | tcache_t *tcache = tsd_tcachep_get_unsafe(tsd); | ||
| 712 | |||
| 713 | assert(cache_bin_still_zero_initialized(&tcache->bins[0])); | ||
| 714 | size_t alignment = tcache_bin_alloc_alignment; | ||
| 715 | size_t size = sz_sa2u(tcache_bin_alloc_size, alignment); | ||
| 716 | |||
| 717 | void *mem = ipallocztm(tsd_tsdn(tsd), size, alignment, true, NULL, | ||
| 718 | true, arena_get(TSDN_NULL, 0, true)); | ||
| 719 | if (mem == NULL) { | ||
| 720 | return true; | ||
| 721 | } | ||
| 722 | |||
| 723 | tcache_init(tsd, tcache_slow, tcache, mem); | ||
| 724 | /* | ||
| 725 | * Initialization is a bit tricky here. After malloc init is done, all | ||
| 726 | * threads can rely on arena_choose and associate tcache accordingly. | ||
| 727 | * However, the thread that does actual malloc bootstrapping relies on | ||
| 728 | * functional tsd, and it can only rely on a0. In that case, we | ||
| 729 | * associate its tcache to a0 temporarily, and later on | ||
| 730 | * arena_choose_hard() will re-associate properly. | ||
| 731 | */ | ||
| 732 | tcache_slow->arena = NULL; | ||
| 733 | arena_t *arena; | ||
| 734 | if (!malloc_initialized()) { | ||
| 735 | /* If in initialization, assign to a0. */ | ||
| 736 | arena = arena_get(tsd_tsdn(tsd), 0, false); | ||
| 737 | tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, tcache, | ||
| 738 | arena); | ||
| 739 | } else { | ||
| 740 | arena = arena_choose(tsd, NULL); | ||
| 741 | /* This may happen if thread.tcache.enabled is used. */ | ||
| 742 | if (tcache_slow->arena == NULL) { | ||
| 743 | tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, | ||
| 744 | tcache, arena); | ||
| 745 | } | ||
| 746 | } | ||
| 747 | assert(arena == tcache_slow->arena); | ||
| 748 | |||
| 749 | return false; | ||
| 750 | } | ||
| 751 | |||
| 752 | /* Created manual tcache for tcache.create mallctl. */ | ||
| 753 | tcache_t * | ||
| 754 | tcache_create_explicit(tsd_t *tsd) { | ||
| 755 | /* | ||
| 756 | * We place the cache bin stacks, then the tcache_t, then a pointer to | ||
| 757 | * the beginning of the whole allocation (for freeing). The makes sure | ||
| 758 | * the cache bins have the requested alignment. | ||
| 759 | */ | ||
| 760 | size_t size = tcache_bin_alloc_size + sizeof(tcache_t) | ||
| 761 | + sizeof(tcache_slow_t); | ||
| 762 | /* Naturally align the pointer stacks. */ | ||
| 763 | size = PTR_CEILING(size); | ||
| 764 | size = sz_sa2u(size, tcache_bin_alloc_alignment); | ||
| 765 | |||
| 766 | void *mem = ipallocztm(tsd_tsdn(tsd), size, tcache_bin_alloc_alignment, | ||
| 767 | true, NULL, true, arena_get(TSDN_NULL, 0, true)); | ||
| 768 | if (mem == NULL) { | ||
| 769 | return NULL; | ||
| 770 | } | ||
| 771 | tcache_t *tcache = (void *)((uintptr_t)mem + tcache_bin_alloc_size); | ||
| 772 | tcache_slow_t *tcache_slow = | ||
| 773 | (void *)((uintptr_t)mem + tcache_bin_alloc_size + sizeof(tcache_t)); | ||
| 774 | tcache_init(tsd, tcache_slow, tcache, mem); | ||
| 775 | |||
| 776 | tcache_arena_associate(tsd_tsdn(tsd), tcache_slow, tcache, | ||
| 777 | arena_ichoose(tsd, NULL)); | ||
| 778 | |||
| 779 | return tcache; | ||
| 780 | } | ||
| 781 | |||
| 782 | static void | ||
| 783 | tcache_flush_cache(tsd_t *tsd, tcache_t *tcache) { | ||
| 784 | tcache_slow_t *tcache_slow = tcache->tcache_slow; | ||
| 785 | assert(tcache_slow->arena != NULL); | ||
| 786 | |||
| 787 | for (unsigned i = 0; i < nhbins; i++) { | ||
| 788 | cache_bin_t *cache_bin = &tcache->bins[i]; | ||
| 789 | if (i < SC_NBINS) { | ||
| 790 | tcache_bin_flush_small(tsd, tcache, cache_bin, i, 0); | ||
| 791 | } else { | ||
| 792 | tcache_bin_flush_large(tsd, tcache, cache_bin, i, 0); | ||
| 793 | } | ||
| 794 | if (config_stats) { | ||
| 795 | assert(cache_bin->tstats.nrequests == 0); | ||
| 796 | } | ||
| 797 | } | ||
| 798 | } | ||
| 799 | |||
| 800 | void | ||
| 801 | tcache_flush(tsd_t *tsd) { | ||
| 802 | assert(tcache_available(tsd)); | ||
| 803 | tcache_flush_cache(tsd, tsd_tcachep_get(tsd)); | ||
| 804 | } | ||
| 805 | |||
| 806 | static void | ||
| 807 | tcache_destroy(tsd_t *tsd, tcache_t *tcache, bool tsd_tcache) { | ||
| 808 | tcache_slow_t *tcache_slow = tcache->tcache_slow; | ||
| 809 | tcache_flush_cache(tsd, tcache); | ||
| 810 | arena_t *arena = tcache_slow->arena; | ||
| 811 | tcache_arena_dissociate(tsd_tsdn(tsd), tcache_slow, tcache); | ||
| 812 | |||
| 813 | if (tsd_tcache) { | ||
| 814 | cache_bin_t *cache_bin = &tcache->bins[0]; | ||
| 815 | cache_bin_assert_empty(cache_bin, &tcache_bin_info[0]); | ||
| 816 | } | ||
| 817 | idalloctm(tsd_tsdn(tsd), tcache_slow->dyn_alloc, NULL, NULL, true, | ||
| 818 | true); | ||
| 819 | |||
| 820 | /* | ||
| 821 | * The deallocation and tcache flush above may not trigger decay since | ||
| 822 | * we are on the tcache shutdown path (potentially with non-nominal | ||
| 823 | * tsd). Manually trigger decay to avoid pathological cases. Also | ||
| 824 | * include arena 0 because the tcache array is allocated from it. | ||
| 825 | */ | ||
| 826 | arena_decay(tsd_tsdn(tsd), arena_get(tsd_tsdn(tsd), 0, false), | ||
| 827 | false, false); | ||
| 828 | |||
| 829 | if (arena_nthreads_get(arena, false) == 0 && | ||
| 830 | !background_thread_enabled()) { | ||
| 831 | /* Force purging when no threads assigned to the arena anymore. */ | ||
| 832 | arena_decay(tsd_tsdn(tsd), arena, | ||
| 833 | /* is_background_thread */ false, /* all */ true); | ||
| 834 | } else { | ||
| 835 | arena_decay(tsd_tsdn(tsd), arena, | ||
| 836 | /* is_background_thread */ false, /* all */ false); | ||
| 837 | } | ||
| 838 | } | ||
| 839 | |||
| 840 | /* For auto tcache (embedded in TSD) only. */ | ||
| 841 | void | ||
| 842 | tcache_cleanup(tsd_t *tsd) { | ||
| 843 | tcache_t *tcache = tsd_tcachep_get(tsd); | ||
| 844 | if (!tcache_available(tsd)) { | ||
| 845 | assert(tsd_tcache_enabled_get(tsd) == false); | ||
| 846 | assert(cache_bin_still_zero_initialized(&tcache->bins[0])); | ||
| 847 | return; | ||
| 848 | } | ||
| 849 | assert(tsd_tcache_enabled_get(tsd)); | ||
| 850 | assert(!cache_bin_still_zero_initialized(&tcache->bins[0])); | ||
| 851 | |||
| 852 | tcache_destroy(tsd, tcache, true); | ||
| 853 | if (config_debug) { | ||
| 854 | /* | ||
| 855 | * For debug testing only, we want to pretend we're still in the | ||
| 856 | * zero-initialized state. | ||
| 857 | */ | ||
| 858 | memset(tcache->bins, 0, sizeof(cache_bin_t) * nhbins); | ||
| 859 | } | ||
| 860 | } | ||
| 861 | |||
| 862 | void | ||
| 863 | tcache_stats_merge(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) { | ||
| 864 | cassert(config_stats); | ||
| 865 | |||
| 866 | /* Merge and reset tcache stats. */ | ||
| 867 | for (unsigned i = 0; i < nhbins; i++) { | ||
| 868 | cache_bin_t *cache_bin = &tcache->bins[i]; | ||
| 869 | if (i < SC_NBINS) { | ||
| 870 | bin_t *bin = arena_bin_choose(tsdn, arena, i, NULL); | ||
| 871 | malloc_mutex_lock(tsdn, &bin->lock); | ||
| 872 | bin->stats.nrequests += cache_bin->tstats.nrequests; | ||
| 873 | malloc_mutex_unlock(tsdn, &bin->lock); | ||
| 874 | } else { | ||
| 875 | arena_stats_large_flush_nrequests_add(tsdn, | ||
| 876 | &arena->stats, i, cache_bin->tstats.nrequests); | ||
| 877 | } | ||
| 878 | cache_bin->tstats.nrequests = 0; | ||
| 879 | } | ||
| 880 | } | ||
| 881 | |||
| 882 | static bool | ||
| 883 | tcaches_create_prep(tsd_t *tsd, base_t *base) { | ||
| 884 | bool err; | ||
| 885 | |||
| 886 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 887 | |||
| 888 | if (tcaches == NULL) { | ||
| 889 | tcaches = base_alloc(tsd_tsdn(tsd), base, | ||
| 890 | sizeof(tcache_t *) * (MALLOCX_TCACHE_MAX+1), CACHELINE); | ||
| 891 | if (tcaches == NULL) { | ||
| 892 | err = true; | ||
| 893 | goto label_return; | ||
| 894 | } | ||
| 895 | } | ||
| 896 | |||
| 897 | if (tcaches_avail == NULL && tcaches_past > MALLOCX_TCACHE_MAX) { | ||
| 898 | err = true; | ||
| 899 | goto label_return; | ||
| 900 | } | ||
| 901 | |||
| 902 | err = false; | ||
| 903 | label_return: | ||
| 904 | return err; | ||
| 905 | } | ||
| 906 | |||
| 907 | bool | ||
| 908 | tcaches_create(tsd_t *tsd, base_t *base, unsigned *r_ind) { | ||
| 909 | witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0); | ||
| 910 | |||
| 911 | bool err; | ||
| 912 | |||
| 913 | malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 914 | |||
| 915 | if (tcaches_create_prep(tsd, base)) { | ||
| 916 | err = true; | ||
| 917 | goto label_return; | ||
| 918 | } | ||
| 919 | |||
| 920 | tcache_t *tcache = tcache_create_explicit(tsd); | ||
| 921 | if (tcache == NULL) { | ||
| 922 | err = true; | ||
| 923 | goto label_return; | ||
| 924 | } | ||
| 925 | |||
| 926 | tcaches_t *elm; | ||
| 927 | if (tcaches_avail != NULL) { | ||
| 928 | elm = tcaches_avail; | ||
| 929 | tcaches_avail = tcaches_avail->next; | ||
| 930 | elm->tcache = tcache; | ||
| 931 | *r_ind = (unsigned)(elm - tcaches); | ||
| 932 | } else { | ||
| 933 | elm = &tcaches[tcaches_past]; | ||
| 934 | elm->tcache = tcache; | ||
| 935 | *r_ind = tcaches_past; | ||
| 936 | tcaches_past++; | ||
| 937 | } | ||
| 938 | |||
| 939 | err = false; | ||
| 940 | label_return: | ||
| 941 | malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 942 | witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0); | ||
| 943 | return err; | ||
| 944 | } | ||
| 945 | |||
| 946 | static tcache_t * | ||
| 947 | tcaches_elm_remove(tsd_t *tsd, tcaches_t *elm, bool allow_reinit) { | ||
| 948 | malloc_mutex_assert_owner(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 949 | |||
| 950 | if (elm->tcache == NULL) { | ||
| 951 | return NULL; | ||
| 952 | } | ||
| 953 | tcache_t *tcache = elm->tcache; | ||
| 954 | if (allow_reinit) { | ||
| 955 | elm->tcache = TCACHES_ELM_NEED_REINIT; | ||
| 956 | } else { | ||
| 957 | elm->tcache = NULL; | ||
| 958 | } | ||
| 959 | |||
| 960 | if (tcache == TCACHES_ELM_NEED_REINIT) { | ||
| 961 | return NULL; | ||
| 962 | } | ||
| 963 | return tcache; | ||
| 964 | } | ||
| 965 | |||
| 966 | void | ||
| 967 | tcaches_flush(tsd_t *tsd, unsigned ind) { | ||
| 968 | malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 969 | tcache_t *tcache = tcaches_elm_remove(tsd, &tcaches[ind], true); | ||
| 970 | malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 971 | if (tcache != NULL) { | ||
| 972 | /* Destroy the tcache; recreate in tcaches_get() if needed. */ | ||
| 973 | tcache_destroy(tsd, tcache, false); | ||
| 974 | } | ||
| 975 | } | ||
| 976 | |||
| 977 | void | ||
| 978 | tcaches_destroy(tsd_t *tsd, unsigned ind) { | ||
| 979 | malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 980 | tcaches_t *elm = &tcaches[ind]; | ||
| 981 | tcache_t *tcache = tcaches_elm_remove(tsd, elm, false); | ||
| 982 | elm->next = tcaches_avail; | ||
| 983 | tcaches_avail = elm; | ||
| 984 | malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx); | ||
| 985 | if (tcache != NULL) { | ||
| 986 | tcache_destroy(tsd, tcache, false); | ||
| 987 | } | ||
| 988 | } | ||
| 989 | |||
| 990 | static unsigned | ||
| 991 | tcache_ncached_max_compute(szind_t szind) { | ||
| 992 | if (szind >= SC_NBINS) { | ||
| 993 | assert(szind < nhbins); | ||
| 994 | return opt_tcache_nslots_large; | ||
| 995 | } | ||
| 996 | unsigned slab_nregs = bin_infos[szind].nregs; | ||
| 997 | |||
| 998 | /* We may modify these values; start with the opt versions. */ | ||
| 999 | unsigned nslots_small_min = opt_tcache_nslots_small_min; | ||
| 1000 | unsigned nslots_small_max = opt_tcache_nslots_small_max; | ||
| 1001 | |||
| 1002 | /* | ||
| 1003 | * Clamp values to meet our constraints -- even, nonzero, min < max, and | ||
| 1004 | * suitable for a cache bin size. | ||
| 1005 | */ | ||
| 1006 | if (opt_tcache_nslots_small_max > CACHE_BIN_NCACHED_MAX) { | ||
| 1007 | nslots_small_max = CACHE_BIN_NCACHED_MAX; | ||
| 1008 | } | ||
| 1009 | if (nslots_small_min % 2 != 0) { | ||
| 1010 | nslots_small_min++; | ||
| 1011 | } | ||
| 1012 | if (nslots_small_max % 2 != 0) { | ||
| 1013 | nslots_small_max--; | ||
| 1014 | } | ||
| 1015 | if (nslots_small_min < 2) { | ||
| 1016 | nslots_small_min = 2; | ||
| 1017 | } | ||
| 1018 | if (nslots_small_max < 2) { | ||
| 1019 | nslots_small_max = 2; | ||
| 1020 | } | ||
| 1021 | if (nslots_small_min > nslots_small_max) { | ||
| 1022 | nslots_small_min = nslots_small_max; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | unsigned candidate; | ||
| 1026 | if (opt_lg_tcache_nslots_mul < 0) { | ||
| 1027 | candidate = slab_nregs >> (-opt_lg_tcache_nslots_mul); | ||
| 1028 | } else { | ||
| 1029 | candidate = slab_nregs << opt_lg_tcache_nslots_mul; | ||
| 1030 | } | ||
| 1031 | if (candidate % 2 != 0) { | ||
| 1032 | /* | ||
| 1033 | * We need the candidate size to be even -- we assume that we | ||
| 1034 | * can divide by two and get a positive number (e.g. when | ||
| 1035 | * flushing). | ||
| 1036 | */ | ||
| 1037 | ++candidate; | ||
| 1038 | } | ||
| 1039 | if (candidate <= nslots_small_min) { | ||
| 1040 | return nslots_small_min; | ||
| 1041 | } else if (candidate <= nslots_small_max) { | ||
| 1042 | return candidate; | ||
| 1043 | } else { | ||
| 1044 | return nslots_small_max; | ||
| 1045 | } | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | bool | ||
| 1049 | tcache_boot(tsdn_t *tsdn, base_t *base) { | ||
| 1050 | tcache_maxclass = sz_s2u(opt_tcache_max); | ||
| 1051 | assert(tcache_maxclass <= TCACHE_MAXCLASS_LIMIT); | ||
| 1052 | nhbins = sz_size2index(tcache_maxclass) + 1; | ||
| 1053 | |||
| 1054 | if (malloc_mutex_init(&tcaches_mtx, "tcaches", WITNESS_RANK_TCACHES, | ||
| 1055 | malloc_mutex_rank_exclusive)) { | ||
| 1056 | return true; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | /* Initialize tcache_bin_info. See comments in tcache_init(). */ | ||
| 1060 | unsigned n_reserved_bins = nhbins < SC_NBINS ? SC_NBINS : nhbins; | ||
| 1061 | size_t size = n_reserved_bins * sizeof(cache_bin_info_t); | ||
| 1062 | tcache_bin_info = (cache_bin_info_t *)base_alloc(tsdn, base, size, | ||
| 1063 | CACHELINE); | ||
| 1064 | if (tcache_bin_info == NULL) { | ||
| 1065 | return true; | ||
| 1066 | } | ||
| 1067 | |||
| 1068 | for (szind_t i = 0; i < nhbins; i++) { | ||
| 1069 | unsigned ncached_max = tcache_ncached_max_compute(i); | ||
| 1070 | cache_bin_info_init(&tcache_bin_info[i], ncached_max); | ||
| 1071 | } | ||
| 1072 | for (szind_t i = nhbins; i < SC_NBINS; i++) { | ||
| 1073 | /* Disabled small bins. */ | ||
| 1074 | cache_bin_info_init(&tcache_bin_info[i], 0); | ||
| 1075 | assert(tcache_small_bin_disabled(i, NULL)); | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | cache_bin_info_compute_alloc(tcache_bin_info, nhbins, | ||
| 1079 | &tcache_bin_alloc_size, &tcache_bin_alloc_alignment); | ||
| 1080 | |||
| 1081 | return false; | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | void | ||
| 1085 | tcache_prefork(tsdn_t *tsdn) { | ||
| 1086 | malloc_mutex_prefork(tsdn, &tcaches_mtx); | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | void | ||
| 1090 | tcache_postfork_parent(tsdn_t *tsdn) { | ||
| 1091 | malloc_mutex_postfork_parent(tsdn, &tcaches_mtx); | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | void | ||
| 1095 | tcache_postfork_child(tsdn_t *tsdn) { | ||
| 1096 | malloc_mutex_postfork_child(tsdn, &tcaches_mtx); | ||
| 1097 | } | ||
| 1098 | |||
| 1099 | void tcache_assert_initialized(tcache_t *tcache) { | ||
| 1100 | assert(!cache_bin_still_zero_initialized(&tcache->bins[0])); | ||
| 1101 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/test_hooks.c b/examples/redis-unstable/deps/jemalloc/src/test_hooks.c deleted file mode 100644 index ace00d9..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/test_hooks.c +++ /dev/null | |||
| @@ -1,12 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | |||
| 3 | /* | ||
| 4 | * The hooks are a little bit screwy -- they're not genuinely exported in the | ||
| 5 | * sense that we want them available to end-users, but we do want them visible | ||
| 6 | * from outside the generated library, so that we can use them in test code. | ||
| 7 | */ | ||
| 8 | JEMALLOC_EXPORT | ||
| 9 | void (*test_hooks_arena_new_hook)() = NULL; | ||
| 10 | |||
| 11 | JEMALLOC_EXPORT | ||
| 12 | void (*test_hooks_libc_hook)() = NULL; | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/thread_event.c b/examples/redis-unstable/deps/jemalloc/src/thread_event.c deleted file mode 100644 index 37eb582..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/thread_event.c +++ /dev/null | |||
| @@ -1,343 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/thread_event.h" | ||
| 5 | |||
| 6 | /* | ||
| 7 | * Signatures for event specific functions. These functions should be defined | ||
| 8 | * by the modules owning each event. The signatures here verify that the | ||
| 9 | * definitions follow the right format. | ||
| 10 | * | ||
| 11 | * The first two are functions computing new / postponed event wait time. New | ||
| 12 | * event wait time is the time till the next event if an event is currently | ||
| 13 | * being triggered; postponed event wait time is the time till the next event | ||
| 14 | * if an event should be triggered but needs to be postponed, e.g. when the TSD | ||
| 15 | * is not nominal or during reentrancy. | ||
| 16 | * | ||
| 17 | * The third is the event handler function, which is called whenever an event | ||
| 18 | * is triggered. The parameter is the elapsed time since the last time an | ||
| 19 | * event of the same type was triggered. | ||
| 20 | */ | ||
| 21 | #define E(event, condition_unused, is_alloc_event_unused) \ | ||
| 22 | uint64_t event##_new_event_wait(tsd_t *tsd); \ | ||
| 23 | uint64_t event##_postponed_event_wait(tsd_t *tsd); \ | ||
| 24 | void event##_event_handler(tsd_t *tsd, uint64_t elapsed); | ||
| 25 | |||
| 26 | ITERATE_OVER_ALL_EVENTS | ||
| 27 | #undef E | ||
| 28 | |||
| 29 | /* Signatures for internal functions fetching elapsed time. */ | ||
| 30 | #define E(event, condition_unused, is_alloc_event_unused) \ | ||
| 31 | static uint64_t event##_fetch_elapsed(tsd_t *tsd); | ||
| 32 | |||
| 33 | ITERATE_OVER_ALL_EVENTS | ||
| 34 | #undef E | ||
| 35 | |||
| 36 | static uint64_t | ||
| 37 | tcache_gc_fetch_elapsed(tsd_t *tsd) { | ||
| 38 | return TE_INVALID_ELAPSED; | ||
| 39 | } | ||
| 40 | |||
| 41 | static uint64_t | ||
| 42 | tcache_gc_dalloc_fetch_elapsed(tsd_t *tsd) { | ||
| 43 | return TE_INVALID_ELAPSED; | ||
| 44 | } | ||
| 45 | |||
| 46 | static uint64_t | ||
| 47 | prof_sample_fetch_elapsed(tsd_t *tsd) { | ||
| 48 | uint64_t last_event = thread_allocated_last_event_get(tsd); | ||
| 49 | uint64_t last_sample_event = prof_sample_last_event_get(tsd); | ||
| 50 | prof_sample_last_event_set(tsd, last_event); | ||
| 51 | return last_event - last_sample_event; | ||
| 52 | } | ||
| 53 | |||
| 54 | static uint64_t | ||
| 55 | stats_interval_fetch_elapsed(tsd_t *tsd) { | ||
| 56 | uint64_t last_event = thread_allocated_last_event_get(tsd); | ||
| 57 | uint64_t last_stats_event = stats_interval_last_event_get(tsd); | ||
| 58 | stats_interval_last_event_set(tsd, last_event); | ||
| 59 | return last_event - last_stats_event; | ||
| 60 | } | ||
| 61 | |||
| 62 | static uint64_t | ||
| 63 | peak_alloc_fetch_elapsed(tsd_t *tsd) { | ||
| 64 | return TE_INVALID_ELAPSED; | ||
| 65 | } | ||
| 66 | |||
| 67 | static uint64_t | ||
| 68 | peak_dalloc_fetch_elapsed(tsd_t *tsd) { | ||
| 69 | return TE_INVALID_ELAPSED; | ||
| 70 | } | ||
| 71 | |||
| 72 | /* Per event facilities done. */ | ||
| 73 | |||
| 74 | static bool | ||
| 75 | te_ctx_has_active_events(te_ctx_t *ctx) { | ||
| 76 | assert(config_debug); | ||
| 77 | #define E(event, condition, alloc_event) \ | ||
| 78 | if (condition && alloc_event == ctx->is_alloc) { \ | ||
| 79 | return true; \ | ||
| 80 | } | ||
| 81 | ITERATE_OVER_ALL_EVENTS | ||
| 82 | #undef E | ||
| 83 | return false; | ||
| 84 | } | ||
| 85 | |||
| 86 | static uint64_t | ||
| 87 | te_next_event_compute(tsd_t *tsd, bool is_alloc) { | ||
| 88 | uint64_t wait = TE_MAX_START_WAIT; | ||
| 89 | #define E(event, condition, alloc_event) \ | ||
| 90 | if (is_alloc == alloc_event && condition) { \ | ||
| 91 | uint64_t event_wait = \ | ||
| 92 | event##_event_wait_get(tsd); \ | ||
| 93 | assert(event_wait <= TE_MAX_START_WAIT); \ | ||
| 94 | if (event_wait > 0U && event_wait < wait) { \ | ||
| 95 | wait = event_wait; \ | ||
| 96 | } \ | ||
| 97 | } | ||
| 98 | |||
| 99 | ITERATE_OVER_ALL_EVENTS | ||
| 100 | #undef E | ||
| 101 | assert(wait <= TE_MAX_START_WAIT); | ||
| 102 | return wait; | ||
| 103 | } | ||
| 104 | |||
| 105 | static void | ||
| 106 | te_assert_invariants_impl(tsd_t *tsd, te_ctx_t *ctx) { | ||
| 107 | uint64_t current_bytes = te_ctx_current_bytes_get(ctx); | ||
| 108 | uint64_t last_event = te_ctx_last_event_get(ctx); | ||
| 109 | uint64_t next_event = te_ctx_next_event_get(ctx); | ||
| 110 | uint64_t next_event_fast = te_ctx_next_event_fast_get(ctx); | ||
| 111 | |||
| 112 | assert(last_event != next_event); | ||
| 113 | if (next_event > TE_NEXT_EVENT_FAST_MAX || !tsd_fast(tsd)) { | ||
| 114 | assert(next_event_fast == 0U); | ||
| 115 | } else { | ||
| 116 | assert(next_event_fast == next_event); | ||
| 117 | } | ||
| 118 | |||
| 119 | /* The subtraction is intentionally susceptible to underflow. */ | ||
| 120 | uint64_t interval = next_event - last_event; | ||
| 121 | |||
| 122 | /* The subtraction is intentionally susceptible to underflow. */ | ||
| 123 | assert(current_bytes - last_event < interval); | ||
| 124 | uint64_t min_wait = te_next_event_compute(tsd, te_ctx_is_alloc(ctx)); | ||
| 125 | /* | ||
| 126 | * next_event should have been pushed up only except when no event is | ||
| 127 | * on and the TSD is just initialized. The last_event == 0U guard | ||
| 128 | * below is stronger than needed, but having an exactly accurate guard | ||
| 129 | * is more complicated to implement. | ||
| 130 | */ | ||
| 131 | assert((!te_ctx_has_active_events(ctx) && last_event == 0U) || | ||
| 132 | interval == min_wait || | ||
| 133 | (interval < min_wait && interval == TE_MAX_INTERVAL)); | ||
| 134 | } | ||
| 135 | |||
| 136 | void | ||
| 137 | te_assert_invariants_debug(tsd_t *tsd) { | ||
| 138 | te_ctx_t ctx; | ||
| 139 | te_ctx_get(tsd, &ctx, true); | ||
| 140 | te_assert_invariants_impl(tsd, &ctx); | ||
| 141 | |||
| 142 | te_ctx_get(tsd, &ctx, false); | ||
| 143 | te_assert_invariants_impl(tsd, &ctx); | ||
| 144 | } | ||
| 145 | |||
| 146 | /* | ||
| 147 | * Synchronization around the fast threshold in tsd -- | ||
| 148 | * There are two threads to consider in the synchronization here: | ||
| 149 | * - The owner of the tsd being updated by a slow path change | ||
| 150 | * - The remote thread, doing that slow path change. | ||
| 151 | * | ||
| 152 | * As a design constraint, we want to ensure that a slow-path transition cannot | ||
| 153 | * be ignored for arbitrarily long, and that if the remote thread causes a | ||
| 154 | * slow-path transition and then communicates with the owner thread that it has | ||
| 155 | * occurred, then the owner will go down the slow path on the next allocator | ||
| 156 | * operation (so that we don't want to just wait until the owner hits its slow | ||
| 157 | * path reset condition on its own). | ||
| 158 | * | ||
| 159 | * Here's our strategy to do that: | ||
| 160 | * | ||
| 161 | * The remote thread will update the slow-path stores to TSD variables, issue a | ||
| 162 | * SEQ_CST fence, and then update the TSD next_event_fast counter. The owner | ||
| 163 | * thread will update next_event_fast, issue an SEQ_CST fence, and then check | ||
| 164 | * its TSD to see if it's on the slow path. | ||
| 165 | |||
| 166 | * This is fairly straightforward when 64-bit atomics are supported. Assume that | ||
| 167 | * the remote fence is sandwiched between two owner fences in the reset pathway. | ||
| 168 | * The case where there is no preceding or trailing owner fence (i.e. because | ||
| 169 | * the owner thread is near the beginning or end of its life) can be analyzed | ||
| 170 | * similarly. The owner store to next_event_fast preceding the earlier owner | ||
| 171 | * fence will be earlier in coherence order than the remote store to it, so that | ||
| 172 | * the owner thread will go down the slow path once the store becomes visible to | ||
| 173 | * it, which is no later than the time of the second fence. | ||
| 174 | |||
| 175 | * The case where we don't support 64-bit atomics is trickier, since word | ||
| 176 | * tearing is possible. We'll repeat the same analysis, and look at the two | ||
| 177 | * owner fences sandwiching the remote fence. The next_event_fast stores done | ||
| 178 | * alongside the earlier owner fence cannot overwrite any of the remote stores | ||
| 179 | * (since they precede the earlier owner fence in sb, which precedes the remote | ||
| 180 | * fence in sc, which precedes the remote stores in sb). After the second owner | ||
| 181 | * fence there will be a re-check of the slow-path variables anyways, so the | ||
| 182 | * "owner will notice that it's on the slow path eventually" guarantee is | ||
| 183 | * satisfied. To make sure that the out-of-band-messaging constraint is as well, | ||
| 184 | * note that either the message passing is sequenced before the second owner | ||
| 185 | * fence (in which case the remote stores happen before the second set of owner | ||
| 186 | * stores, so malloc sees a value of zero for next_event_fast and goes down the | ||
| 187 | * slow path), or it is not (in which case the owner sees the tsd slow-path | ||
| 188 | * writes on its previous update). This leaves open the possibility that the | ||
| 189 | * remote thread will (at some arbitrary point in the future) zero out one half | ||
| 190 | * of the owner thread's next_event_fast, but that's always safe (it just sends | ||
| 191 | * it down the slow path earlier). | ||
| 192 | */ | ||
| 193 | static void | ||
| 194 | te_ctx_next_event_fast_update(te_ctx_t *ctx) { | ||
| 195 | uint64_t next_event = te_ctx_next_event_get(ctx); | ||
| 196 | uint64_t next_event_fast = (next_event <= TE_NEXT_EVENT_FAST_MAX) ? | ||
| 197 | next_event : 0U; | ||
| 198 | te_ctx_next_event_fast_set(ctx, next_event_fast); | ||
| 199 | } | ||
| 200 | |||
| 201 | void | ||
| 202 | te_recompute_fast_threshold(tsd_t *tsd) { | ||
| 203 | if (tsd_state_get(tsd) != tsd_state_nominal) { | ||
| 204 | /* Check first because this is also called on purgatory. */ | ||
| 205 | te_next_event_fast_set_non_nominal(tsd); | ||
| 206 | return; | ||
| 207 | } | ||
| 208 | |||
| 209 | te_ctx_t ctx; | ||
| 210 | te_ctx_get(tsd, &ctx, true); | ||
| 211 | te_ctx_next_event_fast_update(&ctx); | ||
| 212 | te_ctx_get(tsd, &ctx, false); | ||
| 213 | te_ctx_next_event_fast_update(&ctx); | ||
| 214 | |||
| 215 | atomic_fence(ATOMIC_SEQ_CST); | ||
| 216 | if (tsd_state_get(tsd) != tsd_state_nominal) { | ||
| 217 | te_next_event_fast_set_non_nominal(tsd); | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | static void | ||
| 222 | te_adjust_thresholds_helper(tsd_t *tsd, te_ctx_t *ctx, | ||
| 223 | uint64_t wait) { | ||
| 224 | /* | ||
| 225 | * The next threshold based on future events can only be adjusted after | ||
| 226 | * progressing the last_event counter (which is set to current). | ||
| 227 | */ | ||
| 228 | assert(te_ctx_current_bytes_get(ctx) == te_ctx_last_event_get(ctx)); | ||
| 229 | assert(wait <= TE_MAX_START_WAIT); | ||
| 230 | |||
| 231 | uint64_t next_event = te_ctx_last_event_get(ctx) + (wait <= | ||
| 232 | TE_MAX_INTERVAL ? wait : TE_MAX_INTERVAL); | ||
| 233 | te_ctx_next_event_set(tsd, ctx, next_event); | ||
| 234 | } | ||
| 235 | |||
| 236 | static uint64_t | ||
| 237 | te_clip_event_wait(uint64_t event_wait) { | ||
| 238 | assert(event_wait > 0U); | ||
| 239 | if (TE_MIN_START_WAIT > 1U && | ||
| 240 | unlikely(event_wait < TE_MIN_START_WAIT)) { | ||
| 241 | event_wait = TE_MIN_START_WAIT; | ||
| 242 | } | ||
| 243 | if (TE_MAX_START_WAIT < UINT64_MAX && | ||
| 244 | unlikely(event_wait > TE_MAX_START_WAIT)) { | ||
| 245 | event_wait = TE_MAX_START_WAIT; | ||
| 246 | } | ||
| 247 | return event_wait; | ||
| 248 | } | ||
| 249 | |||
| 250 | void | ||
| 251 | te_event_trigger(tsd_t *tsd, te_ctx_t *ctx) { | ||
| 252 | /* usize has already been added to thread_allocated. */ | ||
| 253 | uint64_t bytes_after = te_ctx_current_bytes_get(ctx); | ||
| 254 | /* The subtraction is intentionally susceptible to underflow. */ | ||
| 255 | uint64_t accumbytes = bytes_after - te_ctx_last_event_get(ctx); | ||
| 256 | |||
| 257 | te_ctx_last_event_set(ctx, bytes_after); | ||
| 258 | |||
| 259 | bool allow_event_trigger = tsd_nominal(tsd) && | ||
| 260 | tsd_reentrancy_level_get(tsd) == 0; | ||
| 261 | bool is_alloc = ctx->is_alloc; | ||
| 262 | uint64_t wait = TE_MAX_START_WAIT; | ||
| 263 | |||
| 264 | #define E(event, condition, alloc_event) \ | ||
| 265 | bool is_##event##_triggered = false; \ | ||
| 266 | if (is_alloc == alloc_event && condition) { \ | ||
| 267 | uint64_t event_wait = event##_event_wait_get(tsd); \ | ||
| 268 | assert(event_wait <= TE_MAX_START_WAIT); \ | ||
| 269 | if (event_wait > accumbytes) { \ | ||
| 270 | event_wait -= accumbytes; \ | ||
| 271 | } else if (!allow_event_trigger) { \ | ||
| 272 | event_wait = event##_postponed_event_wait(tsd); \ | ||
| 273 | } else { \ | ||
| 274 | is_##event##_triggered = true; \ | ||
| 275 | event_wait = event##_new_event_wait(tsd); \ | ||
| 276 | } \ | ||
| 277 | event_wait = te_clip_event_wait(event_wait); \ | ||
| 278 | event##_event_wait_set(tsd, event_wait); \ | ||
| 279 | if (event_wait < wait) { \ | ||
| 280 | wait = event_wait; \ | ||
| 281 | } \ | ||
| 282 | } | ||
| 283 | |||
| 284 | ITERATE_OVER_ALL_EVENTS | ||
| 285 | #undef E | ||
| 286 | |||
| 287 | assert(wait <= TE_MAX_START_WAIT); | ||
| 288 | te_adjust_thresholds_helper(tsd, ctx, wait); | ||
| 289 | te_assert_invariants(tsd); | ||
| 290 | |||
| 291 | #define E(event, condition, alloc_event) \ | ||
| 292 | if (is_alloc == alloc_event && condition && \ | ||
| 293 | is_##event##_triggered) { \ | ||
| 294 | assert(allow_event_trigger); \ | ||
| 295 | uint64_t elapsed = event##_fetch_elapsed(tsd); \ | ||
| 296 | event##_event_handler(tsd, elapsed); \ | ||
| 297 | } | ||
| 298 | |||
| 299 | ITERATE_OVER_ALL_EVENTS | ||
| 300 | #undef E | ||
| 301 | |||
| 302 | te_assert_invariants(tsd); | ||
| 303 | } | ||
| 304 | |||
| 305 | static void | ||
| 306 | te_init(tsd_t *tsd, bool is_alloc) { | ||
| 307 | te_ctx_t ctx; | ||
| 308 | te_ctx_get(tsd, &ctx, is_alloc); | ||
| 309 | /* | ||
| 310 | * Reset the last event to current, which starts the events from a clean | ||
| 311 | * state. This is necessary when re-init the tsd event counters. | ||
| 312 | * | ||
| 313 | * The event counters maintain a relationship with the current bytes: | ||
| 314 | * last_event <= current < next_event. When a reinit happens (e.g. | ||
| 315 | * reincarnated tsd), the last event needs progressing because all | ||
| 316 | * events start fresh from the current bytes. | ||
| 317 | */ | ||
| 318 | te_ctx_last_event_set(&ctx, te_ctx_current_bytes_get(&ctx)); | ||
| 319 | |||
| 320 | uint64_t wait = TE_MAX_START_WAIT; | ||
| 321 | #define E(event, condition, alloc_event) \ | ||
| 322 | if (is_alloc == alloc_event && condition) { \ | ||
| 323 | uint64_t event_wait = event##_new_event_wait(tsd); \ | ||
| 324 | event_wait = te_clip_event_wait(event_wait); \ | ||
| 325 | event##_event_wait_set(tsd, event_wait); \ | ||
| 326 | if (event_wait < wait) { \ | ||
| 327 | wait = event_wait; \ | ||
| 328 | } \ | ||
| 329 | } | ||
| 330 | |||
| 331 | ITERATE_OVER_ALL_EVENTS | ||
| 332 | #undef E | ||
| 333 | te_adjust_thresholds_helper(tsd, &ctx, wait); | ||
| 334 | } | ||
| 335 | |||
| 336 | void | ||
| 337 | tsd_te_init(tsd_t *tsd) { | ||
| 338 | /* Make sure no overflow for the bytes accumulated on event_trigger. */ | ||
| 339 | assert(TE_MAX_INTERVAL <= UINT64_MAX - SC_LARGE_MAXCLASS + 1); | ||
| 340 | te_init(tsd, true); | ||
| 341 | te_init(tsd, false); | ||
| 342 | te_assert_invariants(tsd); | ||
| 343 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/ticker.c b/examples/redis-unstable/deps/jemalloc/src/ticker.c deleted file mode 100644 index 790b5c2..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/ticker.c +++ /dev/null | |||
| @@ -1,32 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | /* | ||
| 5 | * To avoid using floating point math down core paths (still necessary because | ||
| 6 | * versions of the glibc dynamic loader that did not preserve xmm registers are | ||
| 7 | * still somewhat common, requiring us to be compilable with -mno-sse), and also | ||
| 8 | * to avoid generally expensive library calls, we use a precomputed table of | ||
| 9 | * values. We want to sample U uniformly on [0, 1], and then compute | ||
| 10 | * ceil(log(u)/log(1-1/nticks)). We're mostly interested in the case where | ||
| 11 | * nticks is reasonably big, so 1/log(1-1/nticks) is well-approximated by | ||
| 12 | * -nticks. | ||
| 13 | * | ||
| 14 | * To compute log(u), we sample an integer in [1, 64] and divide, then just look | ||
| 15 | * up results in a table. As a space-compression mechanism, we store these as | ||
| 16 | * uint8_t by dividing the range (255) by the highest-magnitude value the log | ||
| 17 | * can take on, and using that as a multiplier. We then have to divide by that | ||
| 18 | * multiplier at the end of the computation. | ||
| 19 | * | ||
| 20 | * The values here are computed in src/ticker.py | ||
| 21 | */ | ||
| 22 | |||
| 23 | const uint8_t ticker_geom_table[1 << TICKER_GEOM_NBITS] = { | ||
| 24 | 254, 211, 187, 169, 156, 144, 135, 127, | ||
| 25 | 120, 113, 107, 102, 97, 93, 89, 85, | ||
| 26 | 81, 77, 74, 71, 68, 65, 62, 60, | ||
| 27 | 57, 55, 53, 50, 48, 46, 44, 42, | ||
| 28 | 40, 39, 37, 35, 33, 32, 30, 29, | ||
| 29 | 27, 26, 24, 23, 21, 20, 19, 18, | ||
| 30 | 16, 15, 14, 13, 12, 10, 9, 8, | ||
| 31 | 7, 6, 5, 4, 3, 2, 1, 0 | ||
| 32 | }; | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/ticker.py b/examples/redis-unstable/deps/jemalloc/src/ticker.py deleted file mode 100755 index 3807740..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/ticker.py +++ /dev/null | |||
| @@ -1,15 +0,0 @@ | |||
| 1 | #!/usr/bin/env python3 | ||
| 2 | |||
| 3 | import math | ||
| 4 | |||
| 5 | # Must match TICKER_GEOM_NBITS | ||
| 6 | lg_table_size = 6 | ||
| 7 | table_size = 2**lg_table_size | ||
| 8 | byte_max = 255 | ||
| 9 | mul = math.floor(-byte_max/math.log(1 / table_size)) | ||
| 10 | values = [round(-mul * math.log(i / table_size)) | ||
| 11 | for i in range(1, table_size+1)] | ||
| 12 | print("mul =", mul) | ||
| 13 | print("values:") | ||
| 14 | for i in range(table_size // 8): | ||
| 15 | print(", ".join((str(x) for x in values[i*8 : i*8 + 8]))) | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/tsd.c b/examples/redis-unstable/deps/jemalloc/src/tsd.c deleted file mode 100644 index e8e4f3a..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/tsd.c +++ /dev/null | |||
| @@ -1,549 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/san.h" | ||
| 6 | #include "jemalloc/internal/mutex.h" | ||
| 7 | #include "jemalloc/internal/rtree.h" | ||
| 8 | |||
| 9 | /******************************************************************************/ | ||
| 10 | /* Data. */ | ||
| 11 | |||
| 12 | /* TSD_INITIALIZER triggers "-Wmissing-field-initializer" */ | ||
| 13 | JEMALLOC_DIAGNOSTIC_PUSH | ||
| 14 | JEMALLOC_DIAGNOSTIC_IGNORE_MISSING_STRUCT_FIELD_INITIALIZERS | ||
| 15 | |||
| 16 | #ifdef JEMALLOC_MALLOC_THREAD_CLEANUP | ||
| 17 | JEMALLOC_TSD_TYPE_ATTR(tsd_t) tsd_tls = TSD_INITIALIZER; | ||
| 18 | JEMALLOC_TSD_TYPE_ATTR(bool) JEMALLOC_TLS_MODEL tsd_initialized = false; | ||
| 19 | bool tsd_booted = false; | ||
| 20 | #elif (defined(JEMALLOC_TLS)) | ||
| 21 | JEMALLOC_TSD_TYPE_ATTR(tsd_t) tsd_tls = TSD_INITIALIZER; | ||
| 22 | pthread_key_t tsd_tsd; | ||
| 23 | bool tsd_booted = false; | ||
| 24 | #elif (defined(_WIN32)) | ||
| 25 | DWORD tsd_tsd; | ||
| 26 | tsd_wrapper_t tsd_boot_wrapper = {false, TSD_INITIALIZER}; | ||
| 27 | bool tsd_booted = false; | ||
| 28 | #else | ||
| 29 | |||
| 30 | /* | ||
| 31 | * This contains a mutex, but it's pretty convenient to allow the mutex code to | ||
| 32 | * have a dependency on tsd. So we define the struct here, and only refer to it | ||
| 33 | * by pointer in the header. | ||
| 34 | */ | ||
| 35 | struct tsd_init_head_s { | ||
| 36 | ql_head(tsd_init_block_t) blocks; | ||
| 37 | malloc_mutex_t lock; | ||
| 38 | }; | ||
| 39 | |||
| 40 | pthread_key_t tsd_tsd; | ||
| 41 | tsd_init_head_t tsd_init_head = { | ||
| 42 | ql_head_initializer(blocks), | ||
| 43 | MALLOC_MUTEX_INITIALIZER | ||
| 44 | }; | ||
| 45 | |||
| 46 | tsd_wrapper_t tsd_boot_wrapper = { | ||
| 47 | false, | ||
| 48 | TSD_INITIALIZER | ||
| 49 | }; | ||
| 50 | bool tsd_booted = false; | ||
| 51 | #endif | ||
| 52 | |||
| 53 | JEMALLOC_DIAGNOSTIC_POP | ||
| 54 | |||
| 55 | /******************************************************************************/ | ||
| 56 | |||
| 57 | /* A list of all the tsds in the nominal state. */ | ||
| 58 | typedef ql_head(tsd_t) tsd_list_t; | ||
| 59 | static tsd_list_t tsd_nominal_tsds = ql_head_initializer(tsd_nominal_tsds); | ||
| 60 | static malloc_mutex_t tsd_nominal_tsds_lock; | ||
| 61 | |||
| 62 | /* How many slow-path-enabling features are turned on. */ | ||
| 63 | static atomic_u32_t tsd_global_slow_count = ATOMIC_INIT(0); | ||
| 64 | |||
| 65 | static bool | ||
| 66 | tsd_in_nominal_list(tsd_t *tsd) { | ||
| 67 | tsd_t *tsd_list; | ||
| 68 | bool found = false; | ||
| 69 | /* | ||
| 70 | * We don't know that tsd is nominal; it might not be safe to get data | ||
| 71 | * out of it here. | ||
| 72 | */ | ||
| 73 | malloc_mutex_lock(TSDN_NULL, &tsd_nominal_tsds_lock); | ||
| 74 | ql_foreach(tsd_list, &tsd_nominal_tsds, TSD_MANGLE(tsd_link)) { | ||
| 75 | if (tsd == tsd_list) { | ||
| 76 | found = true; | ||
| 77 | break; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | malloc_mutex_unlock(TSDN_NULL, &tsd_nominal_tsds_lock); | ||
| 81 | return found; | ||
| 82 | } | ||
| 83 | |||
| 84 | static void | ||
| 85 | tsd_add_nominal(tsd_t *tsd) { | ||
| 86 | assert(!tsd_in_nominal_list(tsd)); | ||
| 87 | assert(tsd_state_get(tsd) <= tsd_state_nominal_max); | ||
| 88 | ql_elm_new(tsd, TSD_MANGLE(tsd_link)); | ||
| 89 | malloc_mutex_lock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 90 | ql_tail_insert(&tsd_nominal_tsds, tsd, TSD_MANGLE(tsd_link)); | ||
| 91 | malloc_mutex_unlock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 92 | } | ||
| 93 | |||
| 94 | static void | ||
| 95 | tsd_remove_nominal(tsd_t *tsd) { | ||
| 96 | assert(tsd_in_nominal_list(tsd)); | ||
| 97 | assert(tsd_state_get(tsd) <= tsd_state_nominal_max); | ||
| 98 | malloc_mutex_lock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 99 | ql_remove(&tsd_nominal_tsds, tsd, TSD_MANGLE(tsd_link)); | ||
| 100 | malloc_mutex_unlock(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 101 | } | ||
| 102 | |||
| 103 | static void | ||
| 104 | tsd_force_recompute(tsdn_t *tsdn) { | ||
| 105 | /* | ||
| 106 | * The stores to tsd->state here need to synchronize with the exchange | ||
| 107 | * in tsd_slow_update. | ||
| 108 | */ | ||
| 109 | atomic_fence(ATOMIC_RELEASE); | ||
| 110 | malloc_mutex_lock(tsdn, &tsd_nominal_tsds_lock); | ||
| 111 | tsd_t *remote_tsd; | ||
| 112 | ql_foreach(remote_tsd, &tsd_nominal_tsds, TSD_MANGLE(tsd_link)) { | ||
| 113 | assert(tsd_atomic_load(&remote_tsd->state, ATOMIC_RELAXED) | ||
| 114 | <= tsd_state_nominal_max); | ||
| 115 | tsd_atomic_store(&remote_tsd->state, | ||
| 116 | tsd_state_nominal_recompute, ATOMIC_RELAXED); | ||
| 117 | /* See comments in te_recompute_fast_threshold(). */ | ||
| 118 | atomic_fence(ATOMIC_SEQ_CST); | ||
| 119 | te_next_event_fast_set_non_nominal(remote_tsd); | ||
| 120 | } | ||
| 121 | malloc_mutex_unlock(tsdn, &tsd_nominal_tsds_lock); | ||
| 122 | } | ||
| 123 | |||
| 124 | void | ||
| 125 | tsd_global_slow_inc(tsdn_t *tsdn) { | ||
| 126 | atomic_fetch_add_u32(&tsd_global_slow_count, 1, ATOMIC_RELAXED); | ||
| 127 | /* | ||
| 128 | * We unconditionally force a recompute, even if the global slow count | ||
| 129 | * was already positive. If we didn't, then it would be possible for us | ||
| 130 | * to return to the user, have the user synchronize externally with some | ||
| 131 | * other thread, and then have that other thread not have picked up the | ||
| 132 | * update yet (since the original incrementing thread might still be | ||
| 133 | * making its way through the tsd list). | ||
| 134 | */ | ||
| 135 | tsd_force_recompute(tsdn); | ||
| 136 | } | ||
| 137 | |||
| 138 | void tsd_global_slow_dec(tsdn_t *tsdn) { | ||
| 139 | atomic_fetch_sub_u32(&tsd_global_slow_count, 1, ATOMIC_RELAXED); | ||
| 140 | /* See the note in ..._inc(). */ | ||
| 141 | tsd_force_recompute(tsdn); | ||
| 142 | } | ||
| 143 | |||
| 144 | static bool | ||
| 145 | tsd_local_slow(tsd_t *tsd) { | ||
| 146 | return !tsd_tcache_enabled_get(tsd) | ||
| 147 | || tsd_reentrancy_level_get(tsd) > 0; | ||
| 148 | } | ||
| 149 | |||
| 150 | bool | ||
| 151 | tsd_global_slow() { | ||
| 152 | return atomic_load_u32(&tsd_global_slow_count, ATOMIC_RELAXED) > 0; | ||
| 153 | } | ||
| 154 | |||
| 155 | /******************************************************************************/ | ||
| 156 | |||
| 157 | static uint8_t | ||
| 158 | tsd_state_compute(tsd_t *tsd) { | ||
| 159 | if (!tsd_nominal(tsd)) { | ||
| 160 | return tsd_state_get(tsd); | ||
| 161 | } | ||
| 162 | /* We're in *a* nominal state; but which one? */ | ||
| 163 | if (malloc_slow || tsd_local_slow(tsd) || tsd_global_slow()) { | ||
| 164 | return tsd_state_nominal_slow; | ||
| 165 | } else { | ||
| 166 | return tsd_state_nominal; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | void | ||
| 171 | tsd_slow_update(tsd_t *tsd) { | ||
| 172 | uint8_t old_state; | ||
| 173 | do { | ||
| 174 | uint8_t new_state = tsd_state_compute(tsd); | ||
| 175 | old_state = tsd_atomic_exchange(&tsd->state, new_state, | ||
| 176 | ATOMIC_ACQUIRE); | ||
| 177 | } while (old_state == tsd_state_nominal_recompute); | ||
| 178 | |||
| 179 | te_recompute_fast_threshold(tsd); | ||
| 180 | } | ||
| 181 | |||
| 182 | void | ||
| 183 | tsd_state_set(tsd_t *tsd, uint8_t new_state) { | ||
| 184 | /* Only the tsd module can change the state *to* recompute. */ | ||
| 185 | assert(new_state != tsd_state_nominal_recompute); | ||
| 186 | uint8_t old_state = tsd_atomic_load(&tsd->state, ATOMIC_RELAXED); | ||
| 187 | if (old_state > tsd_state_nominal_max) { | ||
| 188 | /* | ||
| 189 | * Not currently in the nominal list, but it might need to be | ||
| 190 | * inserted there. | ||
| 191 | */ | ||
| 192 | assert(!tsd_in_nominal_list(tsd)); | ||
| 193 | tsd_atomic_store(&tsd->state, new_state, ATOMIC_RELAXED); | ||
| 194 | if (new_state <= tsd_state_nominal_max) { | ||
| 195 | tsd_add_nominal(tsd); | ||
| 196 | } | ||
| 197 | } else { | ||
| 198 | /* | ||
| 199 | * We're currently nominal. If the new state is non-nominal, | ||
| 200 | * great; we take ourselves off the list and just enter the new | ||
| 201 | * state. | ||
| 202 | */ | ||
| 203 | assert(tsd_in_nominal_list(tsd)); | ||
| 204 | if (new_state > tsd_state_nominal_max) { | ||
| 205 | tsd_remove_nominal(tsd); | ||
| 206 | tsd_atomic_store(&tsd->state, new_state, | ||
| 207 | ATOMIC_RELAXED); | ||
| 208 | } else { | ||
| 209 | /* | ||
| 210 | * This is the tricky case. We're transitioning from | ||
| 211 | * one nominal state to another. The caller can't know | ||
| 212 | * about any races that are occurring at the same time, | ||
| 213 | * so we always have to recompute no matter what. | ||
| 214 | */ | ||
| 215 | tsd_slow_update(tsd); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | te_recompute_fast_threshold(tsd); | ||
| 219 | } | ||
| 220 | |||
| 221 | static void | ||
| 222 | tsd_prng_state_init(tsd_t *tsd) { | ||
| 223 | /* | ||
| 224 | * A nondeterministic seed based on the address of tsd reduces | ||
| 225 | * the likelihood of lockstep non-uniform cache index | ||
| 226 | * utilization among identical concurrent processes, but at the | ||
| 227 | * cost of test repeatability. For debug builds, instead use a | ||
| 228 | * deterministic seed. | ||
| 229 | */ | ||
| 230 | *tsd_prng_statep_get(tsd) = config_debug ? 0 : | ||
| 231 | (uint64_t)(uintptr_t)tsd; | ||
| 232 | } | ||
| 233 | |||
| 234 | static bool | ||
| 235 | tsd_data_init(tsd_t *tsd) { | ||
| 236 | /* | ||
| 237 | * We initialize the rtree context first (before the tcache), since the | ||
| 238 | * tcache initialization depends on it. | ||
| 239 | */ | ||
| 240 | rtree_ctx_data_init(tsd_rtree_ctxp_get_unsafe(tsd)); | ||
| 241 | tsd_prng_state_init(tsd); | ||
| 242 | tsd_te_init(tsd); /* event_init may use the prng state above. */ | ||
| 243 | tsd_san_init(tsd); | ||
| 244 | return tsd_tcache_enabled_data_init(tsd); | ||
| 245 | } | ||
| 246 | |||
| 247 | static void | ||
| 248 | assert_tsd_data_cleanup_done(tsd_t *tsd) { | ||
| 249 | assert(!tsd_nominal(tsd)); | ||
| 250 | assert(!tsd_in_nominal_list(tsd)); | ||
| 251 | assert(*tsd_arenap_get_unsafe(tsd) == NULL); | ||
| 252 | assert(*tsd_iarenap_get_unsafe(tsd) == NULL); | ||
| 253 | assert(*tsd_tcache_enabledp_get_unsafe(tsd) == false); | ||
| 254 | assert(*tsd_prof_tdatap_get_unsafe(tsd) == NULL); | ||
| 255 | } | ||
| 256 | |||
| 257 | static bool | ||
| 258 | tsd_data_init_nocleanup(tsd_t *tsd) { | ||
| 259 | assert(tsd_state_get(tsd) == tsd_state_reincarnated || | ||
| 260 | tsd_state_get(tsd) == tsd_state_minimal_initialized); | ||
| 261 | /* | ||
| 262 | * During reincarnation, there is no guarantee that the cleanup function | ||
| 263 | * will be called (deallocation may happen after all tsd destructors). | ||
| 264 | * We set up tsd in a way that no cleanup is needed. | ||
| 265 | */ | ||
| 266 | rtree_ctx_data_init(tsd_rtree_ctxp_get_unsafe(tsd)); | ||
| 267 | *tsd_tcache_enabledp_get_unsafe(tsd) = false; | ||
| 268 | *tsd_reentrancy_levelp_get(tsd) = 1; | ||
| 269 | tsd_prng_state_init(tsd); | ||
| 270 | tsd_te_init(tsd); /* event_init may use the prng state above. */ | ||
| 271 | tsd_san_init(tsd); | ||
| 272 | assert_tsd_data_cleanup_done(tsd); | ||
| 273 | |||
| 274 | return false; | ||
| 275 | } | ||
| 276 | |||
| 277 | tsd_t * | ||
| 278 | tsd_fetch_slow(tsd_t *tsd, bool minimal) { | ||
| 279 | assert(!tsd_fast(tsd)); | ||
| 280 | |||
| 281 | if (tsd_state_get(tsd) == tsd_state_nominal_slow) { | ||
| 282 | /* | ||
| 283 | * On slow path but no work needed. Note that we can't | ||
| 284 | * necessarily *assert* that we're slow, because we might be | ||
| 285 | * slow because of an asynchronous modification to global state, | ||
| 286 | * which might be asynchronously modified *back*. | ||
| 287 | */ | ||
| 288 | } else if (tsd_state_get(tsd) == tsd_state_nominal_recompute) { | ||
| 289 | tsd_slow_update(tsd); | ||
| 290 | } else if (tsd_state_get(tsd) == tsd_state_uninitialized) { | ||
| 291 | if (!minimal) { | ||
| 292 | if (tsd_booted) { | ||
| 293 | tsd_state_set(tsd, tsd_state_nominal); | ||
| 294 | tsd_slow_update(tsd); | ||
| 295 | /* Trigger cleanup handler registration. */ | ||
| 296 | tsd_set(tsd); | ||
| 297 | tsd_data_init(tsd); | ||
| 298 | } | ||
| 299 | } else { | ||
| 300 | tsd_state_set(tsd, tsd_state_minimal_initialized); | ||
| 301 | tsd_set(tsd); | ||
| 302 | tsd_data_init_nocleanup(tsd); | ||
| 303 | } | ||
| 304 | } else if (tsd_state_get(tsd) == tsd_state_minimal_initialized) { | ||
| 305 | if (!minimal) { | ||
| 306 | /* Switch to fully initialized. */ | ||
| 307 | tsd_state_set(tsd, tsd_state_nominal); | ||
| 308 | assert(*tsd_reentrancy_levelp_get(tsd) >= 1); | ||
| 309 | (*tsd_reentrancy_levelp_get(tsd))--; | ||
| 310 | tsd_slow_update(tsd); | ||
| 311 | tsd_data_init(tsd); | ||
| 312 | } else { | ||
| 313 | assert_tsd_data_cleanup_done(tsd); | ||
| 314 | } | ||
| 315 | } else if (tsd_state_get(tsd) == tsd_state_purgatory) { | ||
| 316 | tsd_state_set(tsd, tsd_state_reincarnated); | ||
| 317 | tsd_set(tsd); | ||
| 318 | tsd_data_init_nocleanup(tsd); | ||
| 319 | } else { | ||
| 320 | assert(tsd_state_get(tsd) == tsd_state_reincarnated); | ||
| 321 | } | ||
| 322 | |||
| 323 | return tsd; | ||
| 324 | } | ||
| 325 | |||
| 326 | void * | ||
| 327 | malloc_tsd_malloc(size_t size) { | ||
| 328 | return a0malloc(CACHELINE_CEILING(size)); | ||
| 329 | } | ||
| 330 | |||
| 331 | void | ||
| 332 | malloc_tsd_dalloc(void *wrapper) { | ||
| 333 | a0dalloc(wrapper); | ||
| 334 | } | ||
| 335 | |||
| 336 | #if defined(JEMALLOC_MALLOC_THREAD_CLEANUP) || defined(_WIN32) | ||
| 337 | static unsigned ncleanups; | ||
| 338 | static malloc_tsd_cleanup_t cleanups[MALLOC_TSD_CLEANUPS_MAX]; | ||
| 339 | |||
| 340 | #ifndef _WIN32 | ||
| 341 | JEMALLOC_EXPORT | ||
| 342 | #endif | ||
| 343 | void | ||
| 344 | _malloc_thread_cleanup(void) { | ||
| 345 | bool pending[MALLOC_TSD_CLEANUPS_MAX], again; | ||
| 346 | unsigned i; | ||
| 347 | |||
| 348 | for (i = 0; i < ncleanups; i++) { | ||
| 349 | pending[i] = true; | ||
| 350 | } | ||
| 351 | |||
| 352 | do { | ||
| 353 | again = false; | ||
| 354 | for (i = 0; i < ncleanups; i++) { | ||
| 355 | if (pending[i]) { | ||
| 356 | pending[i] = cleanups[i](); | ||
| 357 | if (pending[i]) { | ||
| 358 | again = true; | ||
| 359 | } | ||
| 360 | } | ||
| 361 | } | ||
| 362 | } while (again); | ||
| 363 | } | ||
| 364 | |||
| 365 | #ifndef _WIN32 | ||
| 366 | JEMALLOC_EXPORT | ||
| 367 | #endif | ||
| 368 | void | ||
| 369 | _malloc_tsd_cleanup_register(bool (*f)(void)) { | ||
| 370 | assert(ncleanups < MALLOC_TSD_CLEANUPS_MAX); | ||
| 371 | cleanups[ncleanups] = f; | ||
| 372 | ncleanups++; | ||
| 373 | } | ||
| 374 | |||
| 375 | #endif | ||
| 376 | |||
| 377 | static void | ||
| 378 | tsd_do_data_cleanup(tsd_t *tsd) { | ||
| 379 | prof_tdata_cleanup(tsd); | ||
| 380 | iarena_cleanup(tsd); | ||
| 381 | arena_cleanup(tsd); | ||
| 382 | tcache_cleanup(tsd); | ||
| 383 | witnesses_cleanup(tsd_witness_tsdp_get_unsafe(tsd)); | ||
| 384 | *tsd_reentrancy_levelp_get(tsd) = 1; | ||
| 385 | } | ||
| 386 | |||
| 387 | void | ||
| 388 | tsd_cleanup(void *arg) { | ||
| 389 | tsd_t *tsd = (tsd_t *)arg; | ||
| 390 | |||
| 391 | switch (tsd_state_get(tsd)) { | ||
| 392 | case tsd_state_uninitialized: | ||
| 393 | /* Do nothing. */ | ||
| 394 | break; | ||
| 395 | case tsd_state_minimal_initialized: | ||
| 396 | /* This implies the thread only did free() in its life time. */ | ||
| 397 | /* Fall through. */ | ||
| 398 | case tsd_state_reincarnated: | ||
| 399 | /* | ||
| 400 | * Reincarnated means another destructor deallocated memory | ||
| 401 | * after the destructor was called. Cleanup isn't required but | ||
| 402 | * is still called for testing and completeness. | ||
| 403 | */ | ||
| 404 | assert_tsd_data_cleanup_done(tsd); | ||
| 405 | JEMALLOC_FALLTHROUGH; | ||
| 406 | case tsd_state_nominal: | ||
| 407 | case tsd_state_nominal_slow: | ||
| 408 | tsd_do_data_cleanup(tsd); | ||
| 409 | tsd_state_set(tsd, tsd_state_purgatory); | ||
| 410 | tsd_set(tsd); | ||
| 411 | break; | ||
| 412 | case tsd_state_purgatory: | ||
| 413 | /* | ||
| 414 | * The previous time this destructor was called, we set the | ||
| 415 | * state to tsd_state_purgatory so that other destructors | ||
| 416 | * wouldn't cause re-creation of the tsd. This time, do | ||
| 417 | * nothing, and do not request another callback. | ||
| 418 | */ | ||
| 419 | break; | ||
| 420 | default: | ||
| 421 | not_reached(); | ||
| 422 | } | ||
| 423 | #ifdef JEMALLOC_JET | ||
| 424 | test_callback_t test_callback = *tsd_test_callbackp_get_unsafe(tsd); | ||
| 425 | int *data = tsd_test_datap_get_unsafe(tsd); | ||
| 426 | if (test_callback != NULL) { | ||
| 427 | test_callback(data); | ||
| 428 | } | ||
| 429 | #endif | ||
| 430 | } | ||
| 431 | |||
| 432 | tsd_t * | ||
| 433 | malloc_tsd_boot0(void) { | ||
| 434 | tsd_t *tsd; | ||
| 435 | |||
| 436 | #if defined(JEMALLOC_MALLOC_THREAD_CLEANUP) || defined(_WIN32) | ||
| 437 | ncleanups = 0; | ||
| 438 | #endif | ||
| 439 | if (malloc_mutex_init(&tsd_nominal_tsds_lock, "tsd_nominal_tsds_lock", | ||
| 440 | WITNESS_RANK_OMIT, malloc_mutex_rank_exclusive)) { | ||
| 441 | return NULL; | ||
| 442 | } | ||
| 443 | if (tsd_boot0()) { | ||
| 444 | return NULL; | ||
| 445 | } | ||
| 446 | tsd = tsd_fetch(); | ||
| 447 | return tsd; | ||
| 448 | } | ||
| 449 | |||
| 450 | void | ||
| 451 | malloc_tsd_boot1(void) { | ||
| 452 | tsd_boot1(); | ||
| 453 | tsd_t *tsd = tsd_fetch(); | ||
| 454 | /* malloc_slow has been set properly. Update tsd_slow. */ | ||
| 455 | tsd_slow_update(tsd); | ||
| 456 | } | ||
| 457 | |||
| 458 | #ifdef _WIN32 | ||
| 459 | static BOOL WINAPI | ||
| 460 | _tls_callback(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { | ||
| 461 | switch (fdwReason) { | ||
| 462 | #ifdef JEMALLOC_LAZY_LOCK | ||
| 463 | case DLL_THREAD_ATTACH: | ||
| 464 | isthreaded = true; | ||
| 465 | break; | ||
| 466 | #endif | ||
| 467 | case DLL_THREAD_DETACH: | ||
| 468 | _malloc_thread_cleanup(); | ||
| 469 | break; | ||
| 470 | default: | ||
| 471 | break; | ||
| 472 | } | ||
| 473 | return true; | ||
| 474 | } | ||
| 475 | |||
| 476 | /* | ||
| 477 | * We need to be able to say "read" here (in the "pragma section"), but have | ||
| 478 | * hooked "read". We won't read for the rest of the file, so we can get away | ||
| 479 | * with unhooking. | ||
| 480 | */ | ||
| 481 | #ifdef read | ||
| 482 | # undef read | ||
| 483 | #endif | ||
| 484 | |||
| 485 | #ifdef _MSC_VER | ||
| 486 | # ifdef _M_IX86 | ||
| 487 | # pragma comment(linker, "/INCLUDE:__tls_used") | ||
| 488 | # pragma comment(linker, "/INCLUDE:_tls_callback") | ||
| 489 | # else | ||
| 490 | # pragma comment(linker, "/INCLUDE:_tls_used") | ||
| 491 | # pragma comment(linker, "/INCLUDE:" STRINGIFY(tls_callback) ) | ||
| 492 | # endif | ||
| 493 | # pragma section(".CRT$XLY",long,read) | ||
| 494 | #endif | ||
| 495 | JEMALLOC_SECTION(".CRT$XLY") JEMALLOC_ATTR(used) | ||
| 496 | BOOL (WINAPI *const tls_callback)(HINSTANCE hinstDLL, | ||
| 497 | DWORD fdwReason, LPVOID lpvReserved) = _tls_callback; | ||
| 498 | #endif | ||
| 499 | |||
| 500 | #if (!defined(JEMALLOC_MALLOC_THREAD_CLEANUP) && !defined(JEMALLOC_TLS) && \ | ||
| 501 | !defined(_WIN32)) | ||
| 502 | void * | ||
| 503 | tsd_init_check_recursion(tsd_init_head_t *head, tsd_init_block_t *block) { | ||
| 504 | pthread_t self = pthread_self(); | ||
| 505 | tsd_init_block_t *iter; | ||
| 506 | |||
| 507 | /* Check whether this thread has already inserted into the list. */ | ||
| 508 | malloc_mutex_lock(TSDN_NULL, &head->lock); | ||
| 509 | ql_foreach(iter, &head->blocks, link) { | ||
| 510 | if (iter->thread == self) { | ||
| 511 | malloc_mutex_unlock(TSDN_NULL, &head->lock); | ||
| 512 | return iter->data; | ||
| 513 | } | ||
| 514 | } | ||
| 515 | /* Insert block into list. */ | ||
| 516 | ql_elm_new(block, link); | ||
| 517 | block->thread = self; | ||
| 518 | ql_tail_insert(&head->blocks, block, link); | ||
| 519 | malloc_mutex_unlock(TSDN_NULL, &head->lock); | ||
| 520 | return NULL; | ||
| 521 | } | ||
| 522 | |||
| 523 | void | ||
| 524 | tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block) { | ||
| 525 | malloc_mutex_lock(TSDN_NULL, &head->lock); | ||
| 526 | ql_remove(&head->blocks, block, link); | ||
| 527 | malloc_mutex_unlock(TSDN_NULL, &head->lock); | ||
| 528 | } | ||
| 529 | #endif | ||
| 530 | |||
| 531 | void | ||
| 532 | tsd_prefork(tsd_t *tsd) { | ||
| 533 | malloc_mutex_prefork(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 534 | } | ||
| 535 | |||
| 536 | void | ||
| 537 | tsd_postfork_parent(tsd_t *tsd) { | ||
| 538 | malloc_mutex_postfork_parent(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 539 | } | ||
| 540 | |||
| 541 | void | ||
| 542 | tsd_postfork_child(tsd_t *tsd) { | ||
| 543 | malloc_mutex_postfork_child(tsd_tsdn(tsd), &tsd_nominal_tsds_lock); | ||
| 544 | ql_new(&tsd_nominal_tsds); | ||
| 545 | |||
| 546 | if (tsd_state_get(tsd) <= tsd_state_nominal_max) { | ||
| 547 | tsd_add_nominal(tsd); | ||
| 548 | } | ||
| 549 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/witness.c b/examples/redis-unstable/deps/jemalloc/src/witness.c deleted file mode 100644 index 4474af0..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/witness.c +++ /dev/null | |||
| @@ -1,122 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | #include "jemalloc/internal/malloc_io.h" | ||
| 6 | |||
| 7 | void | ||
| 8 | witness_init(witness_t *witness, const char *name, witness_rank_t rank, | ||
| 9 | witness_comp_t *comp, void *opaque) { | ||
| 10 | witness->name = name; | ||
| 11 | witness->rank = rank; | ||
| 12 | witness->comp = comp; | ||
| 13 | witness->opaque = opaque; | ||
| 14 | } | ||
| 15 | |||
| 16 | static void | ||
| 17 | witness_print_witness(witness_t *w, unsigned n) { | ||
| 18 | assert(n > 0); | ||
| 19 | if (n == 1) { | ||
| 20 | malloc_printf(" %s(%u)", w->name, w->rank); | ||
| 21 | } else { | ||
| 22 | malloc_printf(" %s(%u)X%u", w->name, w->rank, n); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | static void | ||
| 27 | witness_print_witnesses(const witness_list_t *witnesses) { | ||
| 28 | witness_t *w, *last = NULL; | ||
| 29 | unsigned n = 0; | ||
| 30 | ql_foreach(w, witnesses, link) { | ||
| 31 | if (last != NULL && w->rank > last->rank) { | ||
| 32 | assert(w->name != last->name); | ||
| 33 | witness_print_witness(last, n); | ||
| 34 | n = 0; | ||
| 35 | } else if (last != NULL) { | ||
| 36 | assert(w->rank == last->rank); | ||
| 37 | assert(w->name == last->name); | ||
| 38 | } | ||
| 39 | last = w; | ||
| 40 | ++n; | ||
| 41 | } | ||
| 42 | if (last != NULL) { | ||
| 43 | witness_print_witness(last, n); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | static void | ||
| 48 | witness_lock_error_impl(const witness_list_t *witnesses, | ||
| 49 | const witness_t *witness) { | ||
| 50 | malloc_printf("<jemalloc>: Lock rank order reversal:"); | ||
| 51 | witness_print_witnesses(witnesses); | ||
| 52 | malloc_printf(" %s(%u)\n", witness->name, witness->rank); | ||
| 53 | abort(); | ||
| 54 | } | ||
| 55 | witness_lock_error_t *JET_MUTABLE witness_lock_error = witness_lock_error_impl; | ||
| 56 | |||
| 57 | static void | ||
| 58 | witness_owner_error_impl(const witness_t *witness) { | ||
| 59 | malloc_printf("<jemalloc>: Should own %s(%u)\n", witness->name, | ||
| 60 | witness->rank); | ||
| 61 | abort(); | ||
| 62 | } | ||
| 63 | witness_owner_error_t *JET_MUTABLE witness_owner_error = | ||
| 64 | witness_owner_error_impl; | ||
| 65 | |||
| 66 | static void | ||
| 67 | witness_not_owner_error_impl(const witness_t *witness) { | ||
| 68 | malloc_printf("<jemalloc>: Should not own %s(%u)\n", witness->name, | ||
| 69 | witness->rank); | ||
| 70 | abort(); | ||
| 71 | } | ||
| 72 | witness_not_owner_error_t *JET_MUTABLE witness_not_owner_error = | ||
| 73 | witness_not_owner_error_impl; | ||
| 74 | |||
| 75 | static void | ||
| 76 | witness_depth_error_impl(const witness_list_t *witnesses, | ||
| 77 | witness_rank_t rank_inclusive, unsigned depth) { | ||
| 78 | malloc_printf("<jemalloc>: Should own %u lock%s of rank >= %u:", depth, | ||
| 79 | (depth != 1) ? "s" : "", rank_inclusive); | ||
| 80 | witness_print_witnesses(witnesses); | ||
| 81 | malloc_printf("\n"); | ||
| 82 | abort(); | ||
| 83 | } | ||
| 84 | witness_depth_error_t *JET_MUTABLE witness_depth_error = | ||
| 85 | witness_depth_error_impl; | ||
| 86 | |||
| 87 | void | ||
| 88 | witnesses_cleanup(witness_tsd_t *witness_tsd) { | ||
| 89 | witness_assert_lockless(witness_tsd_tsdn(witness_tsd)); | ||
| 90 | |||
| 91 | /* Do nothing. */ | ||
| 92 | } | ||
| 93 | |||
| 94 | void | ||
| 95 | witness_prefork(witness_tsd_t *witness_tsd) { | ||
| 96 | if (!config_debug) { | ||
| 97 | return; | ||
| 98 | } | ||
| 99 | witness_tsd->forking = true; | ||
| 100 | } | ||
| 101 | |||
| 102 | void | ||
| 103 | witness_postfork_parent(witness_tsd_t *witness_tsd) { | ||
| 104 | if (!config_debug) { | ||
| 105 | return; | ||
| 106 | } | ||
| 107 | witness_tsd->forking = false; | ||
| 108 | } | ||
| 109 | |||
| 110 | void | ||
| 111 | witness_postfork_child(witness_tsd_t *witness_tsd) { | ||
| 112 | if (!config_debug) { | ||
| 113 | return; | ||
| 114 | } | ||
| 115 | #ifndef JEMALLOC_MUTEX_INIT_CB | ||
| 116 | witness_list_t *witnesses; | ||
| 117 | |||
| 118 | witnesses = &witness_tsd->witnesses; | ||
| 119 | ql_new(witnesses); | ||
| 120 | #endif | ||
| 121 | witness_tsd->forking = false; | ||
| 122 | } | ||
diff --git a/examples/redis-unstable/deps/jemalloc/src/zone.c b/examples/redis-unstable/deps/jemalloc/src/zone.c deleted file mode 100644 index 23dfdd0..0000000 --- a/examples/redis-unstable/deps/jemalloc/src/zone.c +++ /dev/null | |||
| @@ -1,469 +0,0 @@ | |||
| 1 | #include "jemalloc/internal/jemalloc_preamble.h" | ||
| 2 | #include "jemalloc/internal/jemalloc_internal_includes.h" | ||
| 3 | |||
| 4 | #include "jemalloc/internal/assert.h" | ||
| 5 | |||
| 6 | #ifndef JEMALLOC_ZONE | ||
| 7 | # error "This source file is for zones on Darwin (OS X)." | ||
| 8 | #endif | ||
| 9 | |||
| 10 | /* Definitions of the following structs in malloc/malloc.h might be too old | ||
| 11 | * for the built binary to run on newer versions of OSX. So use the newest | ||
| 12 | * possible version of those structs. | ||
| 13 | */ | ||
| 14 | typedef struct _malloc_zone_t { | ||
| 15 | void *reserved1; | ||
| 16 | void *reserved2; | ||
| 17 | size_t (*size)(struct _malloc_zone_t *, const void *); | ||
| 18 | void *(*malloc)(struct _malloc_zone_t *, size_t); | ||
| 19 | void *(*calloc)(struct _malloc_zone_t *, size_t, size_t); | ||
| 20 | void *(*valloc)(struct _malloc_zone_t *, size_t); | ||
| 21 | void (*free)(struct _malloc_zone_t *, void *); | ||
| 22 | void *(*realloc)(struct _malloc_zone_t *, void *, size_t); | ||
| 23 | void (*destroy)(struct _malloc_zone_t *); | ||
| 24 | const char *zone_name; | ||
| 25 | unsigned (*batch_malloc)(struct _malloc_zone_t *, size_t, void **, unsigned); | ||
| 26 | void (*batch_free)(struct _malloc_zone_t *, void **, unsigned); | ||
| 27 | struct malloc_introspection_t *introspect; | ||
| 28 | unsigned version; | ||
| 29 | void *(*memalign)(struct _malloc_zone_t *, size_t, size_t); | ||
| 30 | void (*free_definite_size)(struct _malloc_zone_t *, void *, size_t); | ||
| 31 | size_t (*pressure_relief)(struct _malloc_zone_t *, size_t); | ||
| 32 | } malloc_zone_t; | ||
| 33 | |||
| 34 | typedef struct { | ||
| 35 | vm_address_t address; | ||
| 36 | vm_size_t size; | ||
| 37 | } vm_range_t; | ||
| 38 | |||
| 39 | typedef struct malloc_statistics_t { | ||
| 40 | unsigned blocks_in_use; | ||
| 41 | size_t size_in_use; | ||
| 42 | size_t max_size_in_use; | ||
| 43 | size_t size_allocated; | ||
| 44 | } malloc_statistics_t; | ||
| 45 | |||
| 46 | typedef kern_return_t memory_reader_t(task_t, vm_address_t, vm_size_t, void **); | ||
| 47 | |||
| 48 | typedef void vm_range_recorder_t(task_t, void *, unsigned type, vm_range_t *, unsigned); | ||
| 49 | |||
| 50 | typedef struct malloc_introspection_t { | ||
| 51 | kern_return_t (*enumerator)(task_t, void *, unsigned, vm_address_t, memory_reader_t, vm_range_recorder_t); | ||
| 52 | size_t (*good_size)(malloc_zone_t *, size_t); | ||
| 53 | boolean_t (*check)(malloc_zone_t *); | ||
| 54 | void (*print)(malloc_zone_t *, boolean_t); | ||
| 55 | void (*log)(malloc_zone_t *, void *); | ||
| 56 | void (*force_lock)(malloc_zone_t *); | ||
| 57 | void (*force_unlock)(malloc_zone_t *); | ||
| 58 | void (*statistics)(malloc_zone_t *, malloc_statistics_t *); | ||
| 59 | boolean_t (*zone_locked)(malloc_zone_t *); | ||
| 60 | boolean_t (*enable_discharge_checking)(malloc_zone_t *); | ||
| 61 | boolean_t (*disable_discharge_checking)(malloc_zone_t *); | ||
| 62 | void (*discharge)(malloc_zone_t *, void *); | ||
| 63 | #ifdef __BLOCKS__ | ||
| 64 | void (*enumerate_discharged_pointers)(malloc_zone_t *, void (^)(void *, void *)); | ||
| 65 | #else | ||
| 66 | void *enumerate_unavailable_without_blocks; | ||
| 67 | #endif | ||
| 68 | void (*reinit_lock)(malloc_zone_t *); | ||
| 69 | } malloc_introspection_t; | ||
| 70 | |||
| 71 | extern kern_return_t malloc_get_all_zones(task_t, memory_reader_t, vm_address_t **, unsigned *); | ||
| 72 | |||
| 73 | extern malloc_zone_t *malloc_default_zone(void); | ||
| 74 | |||
| 75 | extern void malloc_zone_register(malloc_zone_t *zone); | ||
| 76 | |||
| 77 | extern void malloc_zone_unregister(malloc_zone_t *zone); | ||
| 78 | |||
| 79 | /* | ||
| 80 | * The malloc_default_purgeable_zone() function is only available on >= 10.6. | ||
| 81 | * We need to check whether it is present at runtime, thus the weak_import. | ||
| 82 | */ | ||
| 83 | extern malloc_zone_t *malloc_default_purgeable_zone(void) | ||
| 84 | JEMALLOC_ATTR(weak_import); | ||
| 85 | |||
| 86 | /******************************************************************************/ | ||
| 87 | /* Data. */ | ||
| 88 | |||
| 89 | static malloc_zone_t *default_zone, *purgeable_zone; | ||
| 90 | static malloc_zone_t jemalloc_zone; | ||
| 91 | static struct malloc_introspection_t jemalloc_zone_introspect; | ||
| 92 | static pid_t zone_force_lock_pid = -1; | ||
| 93 | |||
| 94 | /******************************************************************************/ | ||
| 95 | /* Function prototypes for non-inline static functions. */ | ||
| 96 | |||
| 97 | static size_t zone_size(malloc_zone_t *zone, const void *ptr); | ||
| 98 | static void *zone_malloc(malloc_zone_t *zone, size_t size); | ||
| 99 | static void *zone_calloc(malloc_zone_t *zone, size_t num, size_t size); | ||
| 100 | static void *zone_valloc(malloc_zone_t *zone, size_t size); | ||
| 101 | static void zone_free(malloc_zone_t *zone, void *ptr); | ||
| 102 | static void *zone_realloc(malloc_zone_t *zone, void *ptr, size_t size); | ||
| 103 | static void *zone_memalign(malloc_zone_t *zone, size_t alignment, | ||
| 104 | size_t size); | ||
| 105 | static void zone_free_definite_size(malloc_zone_t *zone, void *ptr, | ||
| 106 | size_t size); | ||
| 107 | static void zone_destroy(malloc_zone_t *zone); | ||
| 108 | static unsigned zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, | ||
| 109 | void **results, unsigned num_requested); | ||
| 110 | static void zone_batch_free(struct _malloc_zone_t *zone, | ||
| 111 | void **to_be_freed, unsigned num_to_be_freed); | ||
| 112 | static size_t zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal); | ||
| 113 | static size_t zone_good_size(malloc_zone_t *zone, size_t size); | ||
| 114 | static kern_return_t zone_enumerator(task_t task, void *data, unsigned type_mask, | ||
| 115 | vm_address_t zone_address, memory_reader_t reader, | ||
| 116 | vm_range_recorder_t recorder); | ||
| 117 | static boolean_t zone_check(malloc_zone_t *zone); | ||
| 118 | static void zone_print(malloc_zone_t *zone, boolean_t verbose); | ||
| 119 | static void zone_log(malloc_zone_t *zone, void *address); | ||
| 120 | static void zone_force_lock(malloc_zone_t *zone); | ||
| 121 | static void zone_force_unlock(malloc_zone_t *zone); | ||
| 122 | static void zone_statistics(malloc_zone_t *zone, | ||
| 123 | malloc_statistics_t *stats); | ||
| 124 | static boolean_t zone_locked(malloc_zone_t *zone); | ||
| 125 | static void zone_reinit_lock(malloc_zone_t *zone); | ||
| 126 | |||
| 127 | /******************************************************************************/ | ||
| 128 | /* | ||
| 129 | * Functions. | ||
| 130 | */ | ||
| 131 | |||
| 132 | static size_t | ||
| 133 | zone_size(malloc_zone_t *zone, const void *ptr) { | ||
| 134 | /* | ||
| 135 | * There appear to be places within Darwin (such as setenv(3)) that | ||
| 136 | * cause calls to this function with pointers that *no* zone owns. If | ||
| 137 | * we knew that all pointers were owned by *some* zone, we could split | ||
| 138 | * our zone into two parts, and use one as the default allocator and | ||
| 139 | * the other as the default deallocator/reallocator. Since that will | ||
| 140 | * not work in practice, we must check all pointers to assure that they | ||
| 141 | * reside within a mapped extent before determining size. | ||
| 142 | */ | ||
| 143 | return ivsalloc(tsdn_fetch(), ptr); | ||
| 144 | } | ||
| 145 | |||
| 146 | static void * | ||
| 147 | zone_malloc(malloc_zone_t *zone, size_t size) { | ||
| 148 | return je_malloc(size); | ||
| 149 | } | ||
| 150 | |||
| 151 | static void * | ||
| 152 | zone_calloc(malloc_zone_t *zone, size_t num, size_t size) { | ||
| 153 | return je_calloc(num, size); | ||
| 154 | } | ||
| 155 | |||
| 156 | static void * | ||
| 157 | zone_valloc(malloc_zone_t *zone, size_t size) { | ||
| 158 | void *ret = NULL; /* Assignment avoids useless compiler warning. */ | ||
| 159 | |||
| 160 | je_posix_memalign(&ret, PAGE, size); | ||
| 161 | |||
| 162 | return ret; | ||
| 163 | } | ||
| 164 | |||
| 165 | static void | ||
| 166 | zone_free(malloc_zone_t *zone, void *ptr) { | ||
| 167 | if (ivsalloc(tsdn_fetch(), ptr) != 0) { | ||
| 168 | je_free(ptr); | ||
| 169 | return; | ||
| 170 | } | ||
| 171 | |||
| 172 | free(ptr); | ||
| 173 | } | ||
| 174 | |||
| 175 | static void * | ||
| 176 | zone_realloc(malloc_zone_t *zone, void *ptr, size_t size) { | ||
| 177 | if (ivsalloc(tsdn_fetch(), ptr) != 0) { | ||
| 178 | return je_realloc(ptr, size); | ||
| 179 | } | ||
| 180 | |||
| 181 | return realloc(ptr, size); | ||
| 182 | } | ||
| 183 | |||
| 184 | static void * | ||
| 185 | zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size) { | ||
| 186 | void *ret = NULL; /* Assignment avoids useless compiler warning. */ | ||
| 187 | |||
| 188 | je_posix_memalign(&ret, alignment, size); | ||
| 189 | |||
| 190 | return ret; | ||
| 191 | } | ||
| 192 | |||
| 193 | static void | ||
| 194 | zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size) { | ||
| 195 | size_t alloc_size; | ||
| 196 | |||
| 197 | alloc_size = ivsalloc(tsdn_fetch(), ptr); | ||
| 198 | if (alloc_size != 0) { | ||
| 199 | assert(alloc_size == size); | ||
| 200 | je_free(ptr); | ||
| 201 | return; | ||
| 202 | } | ||
| 203 | |||
| 204 | free(ptr); | ||
| 205 | } | ||
| 206 | |||
| 207 | static void | ||
| 208 | zone_destroy(malloc_zone_t *zone) { | ||
| 209 | /* This function should never be called. */ | ||
| 210 | not_reached(); | ||
| 211 | } | ||
| 212 | |||
| 213 | static unsigned | ||
| 214 | zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results, | ||
| 215 | unsigned num_requested) { | ||
| 216 | unsigned i; | ||
| 217 | |||
| 218 | for (i = 0; i < num_requested; i++) { | ||
| 219 | results[i] = je_malloc(size); | ||
| 220 | if (!results[i]) | ||
| 221 | break; | ||
| 222 | } | ||
| 223 | |||
| 224 | return i; | ||
| 225 | } | ||
| 226 | |||
| 227 | static void | ||
| 228 | zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed, | ||
| 229 | unsigned num_to_be_freed) { | ||
| 230 | unsigned i; | ||
| 231 | |||
| 232 | for (i = 0; i < num_to_be_freed; i++) { | ||
| 233 | zone_free(zone, to_be_freed[i]); | ||
| 234 | to_be_freed[i] = NULL; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | |||
| 238 | static size_t | ||
| 239 | zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal) { | ||
| 240 | return 0; | ||
| 241 | } | ||
| 242 | |||
| 243 | static size_t | ||
| 244 | zone_good_size(malloc_zone_t *zone, size_t size) { | ||
| 245 | if (size == 0) { | ||
| 246 | size = 1; | ||
| 247 | } | ||
| 248 | return sz_s2u(size); | ||
| 249 | } | ||
| 250 | |||
| 251 | static kern_return_t | ||
| 252 | zone_enumerator(task_t task, void *data, unsigned type_mask, | ||
| 253 | vm_address_t zone_address, memory_reader_t reader, | ||
| 254 | vm_range_recorder_t recorder) { | ||
| 255 | return KERN_SUCCESS; | ||
| 256 | } | ||
| 257 | |||
| 258 | static boolean_t | ||
| 259 | zone_check(malloc_zone_t *zone) { | ||
| 260 | return true; | ||
| 261 | } | ||
| 262 | |||
| 263 | static void | ||
| 264 | zone_print(malloc_zone_t *zone, boolean_t verbose) { | ||
| 265 | } | ||
| 266 | |||
| 267 | static void | ||
| 268 | zone_log(malloc_zone_t *zone, void *address) { | ||
| 269 | } | ||
| 270 | |||
| 271 | static void | ||
| 272 | zone_force_lock(malloc_zone_t *zone) { | ||
| 273 | if (isthreaded) { | ||
| 274 | /* | ||
| 275 | * See the note in zone_force_unlock, below, to see why we need | ||
| 276 | * this. | ||
| 277 | */ | ||
| 278 | assert(zone_force_lock_pid == -1); | ||
| 279 | zone_force_lock_pid = getpid(); | ||
| 280 | jemalloc_prefork(); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | static void | ||
| 285 | zone_force_unlock(malloc_zone_t *zone) { | ||
| 286 | /* | ||
| 287 | * zone_force_lock and zone_force_unlock are the entry points to the | ||
| 288 | * forking machinery on OS X. The tricky thing is, the child is not | ||
| 289 | * allowed to unlock mutexes locked in the parent, even if owned by the | ||
| 290 | * forking thread (and the mutex type we use in OS X will fail an assert | ||
| 291 | * if we try). In the child, we can get away with reinitializing all | ||
| 292 | * the mutexes, which has the effect of unlocking them. In the parent, | ||
| 293 | * doing this would mean we wouldn't wake any waiters blocked on the | ||
| 294 | * mutexes we unlock. So, we record the pid of the current thread in | ||
| 295 | * zone_force_lock, and use that to detect if we're in the parent or | ||
| 296 | * child here, to decide which unlock logic we need. | ||
| 297 | */ | ||
| 298 | if (isthreaded) { | ||
| 299 | assert(zone_force_lock_pid != -1); | ||
| 300 | if (getpid() == zone_force_lock_pid) { | ||
| 301 | jemalloc_postfork_parent(); | ||
| 302 | } else { | ||
| 303 | jemalloc_postfork_child(); | ||
| 304 | } | ||
| 305 | zone_force_lock_pid = -1; | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | static void | ||
| 310 | zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { | ||
| 311 | /* We make no effort to actually fill the values */ | ||
| 312 | stats->blocks_in_use = 0; | ||
| 313 | stats->size_in_use = 0; | ||
| 314 | stats->max_size_in_use = 0; | ||
| 315 | stats->size_allocated = 0; | ||
| 316 | } | ||
| 317 | |||
| 318 | static boolean_t | ||
| 319 | zone_locked(malloc_zone_t *zone) { | ||
| 320 | /* Pretend no lock is being held */ | ||
| 321 | return false; | ||
| 322 | } | ||
| 323 | |||
| 324 | static void | ||
| 325 | zone_reinit_lock(malloc_zone_t *zone) { | ||
| 326 | /* As of OSX 10.12, this function is only used when force_unlock would | ||
| 327 | * be used if the zone version were < 9. So just use force_unlock. */ | ||
| 328 | zone_force_unlock(zone); | ||
| 329 | } | ||
| 330 | |||
| 331 | static void | ||
| 332 | zone_init(void) { | ||
| 333 | jemalloc_zone.size = zone_size; | ||
| 334 | jemalloc_zone.malloc = zone_malloc; | ||
| 335 | jemalloc_zone.calloc = zone_calloc; | ||
| 336 | jemalloc_zone.valloc = zone_valloc; | ||
| 337 | jemalloc_zone.free = zone_free; | ||
| 338 | jemalloc_zone.realloc = zone_realloc; | ||
| 339 | jemalloc_zone.destroy = zone_destroy; | ||
| 340 | jemalloc_zone.zone_name = "jemalloc_zone"; | ||
| 341 | jemalloc_zone.batch_malloc = zone_batch_malloc; | ||
| 342 | jemalloc_zone.batch_free = zone_batch_free; | ||
| 343 | jemalloc_zone.introspect = &jemalloc_zone_introspect; | ||
| 344 | jemalloc_zone.version = 9; | ||
| 345 | jemalloc_zone.memalign = zone_memalign; | ||
| 346 | jemalloc_zone.free_definite_size = zone_free_definite_size; | ||
| 347 | jemalloc_zone.pressure_relief = zone_pressure_relief; | ||
| 348 | |||
| 349 | jemalloc_zone_introspect.enumerator = zone_enumerator; | ||
| 350 | jemalloc_zone_introspect.good_size = zone_good_size; | ||
| 351 | jemalloc_zone_introspect.check = zone_check; | ||
| 352 | jemalloc_zone_introspect.print = zone_print; | ||
| 353 | jemalloc_zone_introspect.log = zone_log; | ||
| 354 | jemalloc_zone_introspect.force_lock = zone_force_lock; | ||
| 355 | jemalloc_zone_introspect.force_unlock = zone_force_unlock; | ||
| 356 | jemalloc_zone_introspect.statistics = zone_statistics; | ||
| 357 | jemalloc_zone_introspect.zone_locked = zone_locked; | ||
| 358 | jemalloc_zone_introspect.enable_discharge_checking = NULL; | ||
| 359 | jemalloc_zone_introspect.disable_discharge_checking = NULL; | ||
| 360 | jemalloc_zone_introspect.discharge = NULL; | ||
| 361 | #ifdef __BLOCKS__ | ||
| 362 | jemalloc_zone_introspect.enumerate_discharged_pointers = NULL; | ||
| 363 | #else | ||
| 364 | jemalloc_zone_introspect.enumerate_unavailable_without_blocks = NULL; | ||
| 365 | #endif | ||
| 366 | jemalloc_zone_introspect.reinit_lock = zone_reinit_lock; | ||
| 367 | } | ||
| 368 | |||
| 369 | static malloc_zone_t * | ||
| 370 | zone_default_get(void) { | ||
| 371 | malloc_zone_t **zones = NULL; | ||
| 372 | unsigned int num_zones = 0; | ||
| 373 | |||
| 374 | /* | ||
| 375 | * On OSX 10.12, malloc_default_zone returns a special zone that is not | ||
| 376 | * present in the list of registered zones. That zone uses a "lite zone" | ||
| 377 | * if one is present (apparently enabled when malloc stack logging is | ||
| 378 | * enabled), or the first registered zone otherwise. In practice this | ||
| 379 | * means unless malloc stack logging is enabled, the first registered | ||
| 380 | * zone is the default. So get the list of zones to get the first one, | ||
| 381 | * instead of relying on malloc_default_zone. | ||
| 382 | */ | ||
| 383 | if (KERN_SUCCESS != malloc_get_all_zones(0, NULL, | ||
| 384 | (vm_address_t**)&zones, &num_zones)) { | ||
| 385 | /* | ||
| 386 | * Reset the value in case the failure happened after it was | ||
| 387 | * set. | ||
| 388 | */ | ||
| 389 | num_zones = 0; | ||
| 390 | } | ||
| 391 | |||
| 392 | if (num_zones) { | ||
| 393 | return zones[0]; | ||
| 394 | } | ||
| 395 | |||
| 396 | return malloc_default_zone(); | ||
| 397 | } | ||
| 398 | |||
| 399 | /* As written, this function can only promote jemalloc_zone. */ | ||
| 400 | static void | ||
| 401 | zone_promote(void) { | ||
| 402 | malloc_zone_t *zone; | ||
| 403 | |||
| 404 | do { | ||
| 405 | /* | ||
| 406 | * Unregister and reregister the default zone. On OSX >= 10.6, | ||
| 407 | * unregistering takes the last registered zone and places it | ||
| 408 | * at the location of the specified zone. Unregistering the | ||
| 409 | * default zone thus makes the last registered one the default. | ||
| 410 | * On OSX < 10.6, unregistering shifts all registered zones. | ||
| 411 | * The first registered zone then becomes the default. | ||
| 412 | */ | ||
| 413 | malloc_zone_unregister(default_zone); | ||
| 414 | malloc_zone_register(default_zone); | ||
| 415 | |||
| 416 | /* | ||
| 417 | * On OSX 10.6, having the default purgeable zone appear before | ||
| 418 | * the default zone makes some things crash because it thinks it | ||
| 419 | * owns the default zone allocated pointers. We thus | ||
| 420 | * unregister/re-register it in order to ensure it's always | ||
| 421 | * after the default zone. On OSX < 10.6, there is no purgeable | ||
| 422 | * zone, so this does nothing. On OSX >= 10.6, unregistering | ||
| 423 | * replaces the purgeable zone with the last registered zone | ||
| 424 | * above, i.e. the default zone. Registering it again then puts | ||
| 425 | * it at the end, obviously after the default zone. | ||
| 426 | */ | ||
| 427 | if (purgeable_zone != NULL) { | ||
| 428 | malloc_zone_unregister(purgeable_zone); | ||
| 429 | malloc_zone_register(purgeable_zone); | ||
| 430 | } | ||
| 431 | |||
| 432 | zone = zone_default_get(); | ||
| 433 | } while (zone != &jemalloc_zone); | ||
| 434 | } | ||
| 435 | |||
| 436 | JEMALLOC_ATTR(constructor) | ||
| 437 | void | ||
| 438 | zone_register(void) { | ||
| 439 | /* | ||
| 440 | * If something else replaced the system default zone allocator, don't | ||
| 441 | * register jemalloc's. | ||
| 442 | */ | ||
| 443 | default_zone = zone_default_get(); | ||
| 444 | if (!default_zone->zone_name || strcmp(default_zone->zone_name, | ||
| 445 | "DefaultMallocZone") != 0) { | ||
| 446 | return; | ||
| 447 | } | ||
| 448 | |||
| 449 | /* | ||
| 450 | * The default purgeable zone is created lazily by OSX's libc. It uses | ||
| 451 | * the default zone when it is created for "small" allocations | ||
| 452 | * (< 15 KiB), but assumes the default zone is a scalable_zone. This | ||
| 453 | * obviously fails when the default zone is the jemalloc zone, so | ||
| 454 | * malloc_default_purgeable_zone() is called beforehand so that the | ||
| 455 | * default purgeable zone is created when the default zone is still | ||
| 456 | * a scalable_zone. As purgeable zones only exist on >= 10.6, we need | ||
| 457 | * to check for the existence of malloc_default_purgeable_zone() at | ||
| 458 | * run time. | ||
| 459 | */ | ||
| 460 | purgeable_zone = (malloc_default_purgeable_zone == NULL) ? NULL : | ||
| 461 | malloc_default_purgeable_zone(); | ||
| 462 | |||
| 463 | /* Register the custom zone. At this point it won't be the default. */ | ||
| 464 | zone_init(); | ||
| 465 | malloc_zone_register(&jemalloc_zone); | ||
| 466 | |||
| 467 | /* Promote the custom zone to be default. */ | ||
| 468 | zone_promote(); | ||
| 469 | } | ||
