mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-07-19 07:07:18 +03:00
400 lines
11 KiB
C
400 lines
11 KiB
C
#ifndef JEMALLOC_INTERNAL_ARENA_INLINES_H
|
|
#define JEMALLOC_INTERNAL_ARENA_INLINES_H
|
|
|
|
#include "jemalloc/internal/jemalloc_preamble.h"
|
|
#include "jemalloc/internal/arena.h"
|
|
#include "jemalloc/internal/arenas_management.h"
|
|
#include "jemalloc/internal/bin_inlines.h"
|
|
#include "jemalloc/internal/div.h"
|
|
#include "jemalloc/internal/emap.h"
|
|
#include "jemalloc/internal/extent.h"
|
|
#include "jemalloc/internal/jemalloc_internal_inlines_a.h"
|
|
#include "jemalloc/internal/jemalloc_internal_types.h"
|
|
#include "jemalloc/internal/large.h"
|
|
#include "jemalloc/internal/mutex.h"
|
|
#include "jemalloc/internal/prof.h"
|
|
#include "jemalloc/internal/rtree.h"
|
|
#include "jemalloc/internal/safety_check.h"
|
|
#include "jemalloc/internal/sc.h"
|
|
#include "jemalloc/internal/sz.h"
|
|
#include "jemalloc/internal/tcache.h"
|
|
#include "jemalloc/internal/ticker.h"
|
|
|
|
/* Cheap field accessors. */
|
|
|
|
static inline unsigned
|
|
arena_ind_get(const arena_t *arena) {
|
|
return arena->ind;
|
|
}
|
|
|
|
static inline void
|
|
arena_internal_add(arena_t *arena, size_t size) {
|
|
atomic_fetch_add_zu(&arena->stats.internal, size, ATOMIC_RELAXED);
|
|
}
|
|
|
|
static inline void
|
|
arena_internal_sub(arena_t *arena, size_t size) {
|
|
atomic_fetch_sub_zu(&arena->stats.internal, size, ATOMIC_RELAXED);
|
|
}
|
|
|
|
static inline size_t
|
|
arena_internal_get(const arena_t *arena) {
|
|
return atomic_load_zu(&arena->stats.internal, ATOMIC_RELAXED);
|
|
}
|
|
|
|
static inline bool
|
|
arena_is_auto(const arena_t *arena) {
|
|
assert(narenas_auto > 0);
|
|
|
|
return (arena_ind_get(arena) < manual_arena_base);
|
|
}
|
|
|
|
static inline arena_t *
|
|
arena_get_from_edata(const edata_t *edata) {
|
|
return (arena_t *)atomic_load_p(
|
|
&arenas[edata_arena_ind_get(edata)], ATOMIC_RELAXED);
|
|
}
|
|
|
|
/* Arena selection and migration. */
|
|
|
|
static inline void
|
|
thread_migrate_arena(tsd_t *tsd, arena_t *oldarena, arena_t *newarena) {
|
|
assert(oldarena != NULL);
|
|
assert(newarena != NULL);
|
|
|
|
arena_migrate(tsd, oldarena, newarena);
|
|
if (tcache_available(tsd)) {
|
|
tcache_arena_reassociate(tsd_tsdn(tsd),
|
|
tsd_tcache_slowp_get(tsd), newarena);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
percpu_arena_update(tsd_t *tsd, unsigned cpu) {
|
|
assert(have_percpu_arena);
|
|
arena_t *oldarena = tsd_arena_get(tsd);
|
|
assert(oldarena != NULL);
|
|
unsigned oldind = arena_ind_get(oldarena);
|
|
|
|
if (oldind != cpu) {
|
|
unsigned newind = cpu;
|
|
arena_t *newarena = arena_get(tsd_tsdn(tsd), newind, true);
|
|
assert(newarena != NULL);
|
|
|
|
thread_migrate_arena(tsd, oldarena, newarena);
|
|
}
|
|
}
|
|
|
|
/* Choose an arena based on a per-thread value. */
|
|
static inline arena_t *
|
|
arena_choose_impl(tsd_t *tsd, arena_t *arena, bool internal) {
|
|
arena_t *ret;
|
|
|
|
if (arena != NULL) {
|
|
return arena;
|
|
}
|
|
|
|
/* During reentrancy, arena 0 is the safest bet. */
|
|
if (unlikely(tsd_reentrancy_level_get(tsd) > 0)) {
|
|
return arena_get(tsd_tsdn(tsd), 0, true);
|
|
}
|
|
|
|
ret = internal ? tsd_iarena_get(tsd) : tsd_arena_get(tsd);
|
|
if (unlikely(ret == NULL)) {
|
|
ret = arena_choose_hard(tsd, internal);
|
|
assert(ret);
|
|
if (tcache_available(tsd)) {
|
|
tcache_slow_t *tcache_slow = tsd_tcache_slowp_get(tsd);
|
|
if (tcache_slow->arena != NULL) {
|
|
/* See comments in tsd_tcache_data_init().*/
|
|
assert(tcache_slow->arena
|
|
== arena_get(tsd_tsdn(tsd), 0, false));
|
|
if (tcache_slow->arena != ret) {
|
|
tcache_arena_reassociate(tsd_tsdn(tsd),
|
|
tcache_slow, ret);
|
|
}
|
|
} else {
|
|
tcache_arena_associate(
|
|
tsd_tsdn(tsd), tcache_slow, ret);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Note that for percpu arena, if the current arena is outside of the
|
|
* auto percpu arena range, (i.e. thread is assigned to a manually
|
|
* managed arena), then percpu arena is skipped.
|
|
*/
|
|
if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)
|
|
&& !internal
|
|
&& (arena_ind_get(ret) < percpu_arena_ind_limit(opt_percpu_arena))
|
|
&& (ret->last_thd != tsd_tsdn(tsd))) {
|
|
unsigned ind = percpu_arena_choose();
|
|
if (arena_ind_get(ret) != ind) {
|
|
percpu_arena_update(tsd, ind);
|
|
ret = tsd_arena_get(tsd);
|
|
}
|
|
ret->last_thd = tsd_tsdn(tsd);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline arena_t *
|
|
arena_choose(tsd_t *tsd, arena_t *arena) {
|
|
return arena_choose_impl(tsd, arena, false);
|
|
}
|
|
|
|
static inline arena_t *
|
|
arena_ichoose(tsd_t *tsd, arena_t *arena) {
|
|
return arena_choose_impl(tsd, arena, true);
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE arena_t *
|
|
arena_choose_maybe_huge(tsd_t *tsd, arena_t *arena, size_t size) {
|
|
if (arena != NULL) {
|
|
return arena;
|
|
}
|
|
|
|
/*
|
|
* For huge allocations, use the dedicated huge arena if both are true:
|
|
* 1) is using auto arena selection (i.e. arena == NULL), and 2) the
|
|
* thread is not assigned to a manual arena.
|
|
*/
|
|
arena_t *tsd_arena = tsd_arena_get(tsd);
|
|
if (tsd_arena == NULL) {
|
|
tsd_arena = arena_choose(tsd, NULL);
|
|
}
|
|
|
|
size_t threshold = atomic_load_zu(
|
|
&tsd_arena->pa_shard.pac.oversize_threshold, ATOMIC_RELAXED);
|
|
if (unlikely(size >= threshold) && arena_is_auto(tsd_arena)) {
|
|
return arena_choose_huge(tsd);
|
|
}
|
|
|
|
return tsd_arena;
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE bool
|
|
large_dalloc_safety_checks(edata_t *edata, const void *ptr, size_t input_size) {
|
|
if (!config_opt_safety_checks) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Eagerly detect double free and sized dealloc bugs for large sizes.
|
|
* The cost is low enough (as edata will be accessed anyway) to be
|
|
* enabled all the time.
|
|
*/
|
|
if (unlikely(edata == NULL
|
|
|| edata_state_get(edata) != extent_state_active)) {
|
|
safety_check_fail(
|
|
"Invalid deallocation detected: "
|
|
"pages being freed (%p) not currently active, "
|
|
"possibly caused by double free bugs.",
|
|
ptr);
|
|
return true;
|
|
}
|
|
if (unlikely(input_size != edata_usize_get(edata)
|
|
|| input_size > SC_LARGE_MAXCLASS)) {
|
|
safety_check_fail_sized_dealloc(/* current_dealloc */ true, ptr,
|
|
/* true_size */ edata_usize_get(edata), input_size);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE void
|
|
arena_prof_info_get(tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx,
|
|
prof_info_t *prof_info, bool reset_recent) {
|
|
cassert(config_prof);
|
|
assert(ptr != NULL);
|
|
assert(prof_info != NULL);
|
|
|
|
edata_t *edata = NULL;
|
|
bool is_slab;
|
|
|
|
/* Static check. */
|
|
if (alloc_ctx == NULL) {
|
|
edata = emap_edata_lookup(
|
|
tsd_tsdn(tsd), &arena_emap_global, ptr);
|
|
is_slab = edata_slab_get(edata);
|
|
} else if (unlikely(!(is_slab = alloc_ctx->slab))) {
|
|
edata = emap_edata_lookup(
|
|
tsd_tsdn(tsd), &arena_emap_global, ptr);
|
|
}
|
|
|
|
if (unlikely(!is_slab)) {
|
|
/* edata must have been initialized at this point. */
|
|
assert(edata != NULL);
|
|
size_t usize = (alloc_ctx == NULL)
|
|
? edata_usize_get(edata)
|
|
: emap_alloc_ctx_usize_get(alloc_ctx);
|
|
if (reset_recent
|
|
&& large_dalloc_safety_checks(edata, ptr, usize)) {
|
|
prof_info->alloc_tctx = PROF_TCTX_SENTINEL;
|
|
return;
|
|
}
|
|
large_prof_info_get(tsd, edata, prof_info, reset_recent);
|
|
} else {
|
|
prof_info->alloc_tctx = PROF_TCTX_SENTINEL;
|
|
/*
|
|
* No need to set other fields in prof_info; they will never be
|
|
* accessed if alloc_tctx == PROF_TCTX_SENTINEL.
|
|
*/
|
|
}
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE void
|
|
arena_prof_tctx_reset(
|
|
tsd_t *tsd, const void *ptr, emap_alloc_ctx_t *alloc_ctx) {
|
|
cassert(config_prof);
|
|
assert(ptr != NULL);
|
|
|
|
/* Static check. */
|
|
if (alloc_ctx == NULL) {
|
|
edata_t *edata = emap_edata_lookup(
|
|
tsd_tsdn(tsd), &arena_emap_global, ptr);
|
|
if (unlikely(!edata_slab_get(edata))) {
|
|
large_prof_tctx_reset(edata);
|
|
}
|
|
} else {
|
|
if (unlikely(!alloc_ctx->slab)) {
|
|
edata_t *edata = emap_edata_lookup(
|
|
tsd_tsdn(tsd), &arena_emap_global, ptr);
|
|
large_prof_tctx_reset(edata);
|
|
}
|
|
}
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE void
|
|
arena_prof_tctx_reset_sampled(tsd_t *tsd, const void *ptr) {
|
|
cassert(config_prof);
|
|
assert(ptr != NULL);
|
|
|
|
edata_t *edata = emap_edata_lookup(
|
|
tsd_tsdn(tsd), &arena_emap_global, ptr);
|
|
assert(!edata_slab_get(edata));
|
|
|
|
large_prof_tctx_reset(edata);
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE void
|
|
arena_prof_info_set(
|
|
tsd_t *tsd, edata_t *edata, prof_tctx_t *tctx, size_t size) {
|
|
cassert(config_prof);
|
|
|
|
assert(!edata_slab_get(edata));
|
|
large_prof_info_set(edata, tctx, size);
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE void
|
|
arena_decay_ticks(tsdn_t *tsdn, arena_t *arena, unsigned nticks) {
|
|
if (unlikely(tsdn_null(tsdn))) {
|
|
return;
|
|
}
|
|
tsd_t *tsd = tsdn_tsd(tsdn);
|
|
/*
|
|
* We use the ticker_geom_t to avoid having per-arena state in the tsd.
|
|
* Instead of having a countdown-until-decay timer running for every
|
|
* arena in every thread, we flip a coin once per tick, whose
|
|
* probability of coming up heads is 1/nticks; this is effectively the
|
|
* operation of the ticker_geom_t. Each arena has the same chance of a
|
|
* coinflip coming up heads (1/ARENA_DECAY_NTICKS_PER_UPDATE), so we can
|
|
* use a single ticker for all of them.
|
|
*/
|
|
ticker_geom_t *decay_ticker = tsd_arena_decay_tickerp_get(tsd);
|
|
uint64_t *prng_state = tsd_prng_statep_get(tsd);
|
|
if (unlikely(ticker_geom_ticks(decay_ticker, prng_state, nticks,
|
|
tsd_reentrancy_level_get(tsd) > 0))) {
|
|
pa_shard_do_deferred_work(tsdn, &arena->pa_shard, false);
|
|
}
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE void
|
|
arena_decay_tick(tsdn_t *tsdn, arena_t *arena) {
|
|
arena_decay_ticks(tsdn, arena, 1);
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE arena_t *
|
|
arena_aalloc(tsdn_t *tsdn, const void *ptr) {
|
|
edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, ptr);
|
|
unsigned arena_ind = edata_arena_ind_get(edata);
|
|
return (arena_t *)atomic_load_p(&arenas[arena_ind], ATOMIC_RELAXED);
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE size_t
|
|
arena_salloc(tsdn_t *tsdn, const void *ptr) {
|
|
assert(ptr != NULL);
|
|
emap_alloc_ctx_t alloc_ctx;
|
|
emap_alloc_ctx_lookup(tsdn, &arena_emap_global, ptr, &alloc_ctx);
|
|
assert(alloc_ctx.szind != SC_NSIZES);
|
|
|
|
return emap_alloc_ctx_usize_get(&alloc_ctx);
|
|
}
|
|
|
|
JEMALLOC_ALWAYS_INLINE size_t
|
|
arena_vsalloc(tsdn_t *tsdn, const void *ptr) {
|
|
/*
|
|
* Return 0 if ptr is not within an extent managed by jemalloc. This
|
|
* function has two extra costs relative to isalloc():
|
|
* - The rtree calls cannot claim to be dependent lookups, which induces
|
|
* rtree lookup load dependencies.
|
|
* - The lookup may fail, so there is an extra branch to check for
|
|
* failure.
|
|
*/
|
|
|
|
emap_full_alloc_ctx_t full_alloc_ctx;
|
|
bool missing = emap_full_alloc_ctx_try_lookup(
|
|
tsdn, &arena_emap_global, ptr, &full_alloc_ctx);
|
|
if (missing) {
|
|
return 0;
|
|
}
|
|
|
|
if (full_alloc_ctx.edata == NULL) {
|
|
return 0;
|
|
}
|
|
assert(edata_state_get(full_alloc_ctx.edata) == extent_state_active);
|
|
/* Only slab members should be looked up via interior pointers. */
|
|
assert(edata_addr_get(full_alloc_ctx.edata) == ptr
|
|
|| edata_slab_get(full_alloc_ctx.edata));
|
|
|
|
assert(full_alloc_ctx.szind != SC_NSIZES);
|
|
|
|
return edata_usize_get(full_alloc_ctx.edata);
|
|
}
|
|
|
|
static inline void
|
|
arena_cache_oblivious_randomize(
|
|
tsdn_t *tsdn, arena_t *arena, edata_t *edata, size_t alignment) {
|
|
assert(edata_base_get(edata) == edata_addr_get(edata));
|
|
|
|
if (alignment < PAGE) {
|
|
unsigned lg_range = LG_PAGE
|
|
- lg_floor(CACHELINE_CEILING(alignment));
|
|
size_t r;
|
|
if (!tsdn_null(tsdn)) {
|
|
tsd_t *tsd = tsdn_tsd(tsdn);
|
|
r = (size_t)prng_lg_range_u64(
|
|
tsd_prng_statep_get(tsd), lg_range);
|
|
} else {
|
|
uint64_t stack_value = (uint64_t)(uintptr_t)&r;
|
|
r = (size_t)prng_lg_range_u64(&stack_value, lg_range);
|
|
}
|
|
uintptr_t random_offset = ((uintptr_t)r)
|
|
<< (LG_PAGE - lg_range);
|
|
edata->e_addr = (void *)((byte_t *)edata->e_addr
|
|
+ random_offset);
|
|
assert(ALIGNMENT_ADDR2BASE(edata->e_addr, alignment)
|
|
== edata->e_addr);
|
|
}
|
|
}
|
|
|
|
static inline bin_t *
|
|
arena_get_bin(arena_t *arena, szind_t binind, unsigned binshard) {
|
|
bin_t *shard0 = (bin_t *)((byte_t *)arena + arena_bin_offsets[binind]);
|
|
return shard0 + binshard;
|
|
}
|
|
|
|
#endif /* JEMALLOC_INTERNAL_ARENA_INLINES_H */
|