add636596a
Now that all merging go through try_acquire_edata_neighbor, the mergeablility checks (including head state checking) are done before reaching the merge hook. In other words, merge hook will never be called if the head state doesn't agree.
275 lines
7.8 KiB
C
275 lines
7.8 KiB
C
#include "jemalloc/internal/jemalloc_preamble.h"
|
|
#include "jemalloc/internal/jemalloc_internal_includes.h"
|
|
|
|
#include "jemalloc/internal/ehooks.h"
|
|
#include "jemalloc/internal/extent_mmap.h"
|
|
|
|
void
|
|
ehooks_init(ehooks_t *ehooks, extent_hooks_t *extent_hooks, unsigned ind) {
|
|
/* All other hooks are optional; this one is not. */
|
|
assert(extent_hooks->alloc != NULL);
|
|
ehooks->ind = ind;
|
|
ehooks_set_extent_hooks_ptr(ehooks, extent_hooks);
|
|
}
|
|
|
|
/*
|
|
* If the caller specifies (!*zero), it is still possible to receive zeroed
|
|
* memory, in which case *zero is toggled to true. arena_extent_alloc() takes
|
|
* advantage of this to avoid demanding zeroed extents, but taking advantage of
|
|
* them if they are returned.
|
|
*/
|
|
static void *
|
|
extent_alloc_core(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size,
|
|
size_t alignment, bool *zero, bool *commit, dss_prec_t dss_prec) {
|
|
void *ret;
|
|
|
|
assert(size != 0);
|
|
assert(alignment != 0);
|
|
|
|
/* "primary" dss. */
|
|
if (have_dss && dss_prec == dss_prec_primary && (ret =
|
|
extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero,
|
|
commit)) != NULL) {
|
|
return ret;
|
|
}
|
|
/* mmap. */
|
|
if ((ret = extent_alloc_mmap(new_addr, size, alignment, zero, commit))
|
|
!= NULL) {
|
|
return ret;
|
|
}
|
|
/* "secondary" dss. */
|
|
if (have_dss && dss_prec == dss_prec_secondary && (ret =
|
|
extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero,
|
|
commit)) != NULL) {
|
|
return ret;
|
|
}
|
|
|
|
/* All strategies for allocation failed. */
|
|
return NULL;
|
|
}
|
|
|
|
void *
|
|
ehooks_default_alloc_impl(tsdn_t *tsdn, void *new_addr, size_t size,
|
|
size_t alignment, bool *zero, bool *commit, unsigned arena_ind) {
|
|
arena_t *arena = arena_get(tsdn, arena_ind, false);
|
|
void *ret = extent_alloc_core(tsdn, arena, new_addr, size, alignment, zero,
|
|
commit, (dss_prec_t)atomic_load_u(&arena->dss_prec,
|
|
ATOMIC_RELAXED));
|
|
if (have_madvise_huge && ret) {
|
|
pages_set_thp_state(ret, size);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void *
|
|
ehooks_default_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size,
|
|
size_t alignment, bool *zero, bool *commit, unsigned arena_ind) {
|
|
tsdn_t *tsdn;
|
|
arena_t *arena;
|
|
|
|
tsdn = tsdn_fetch();
|
|
arena = arena_get(tsdn, arena_ind, false);
|
|
/*
|
|
* The arena we're allocating on behalf of must have been initialized
|
|
* already.
|
|
*/
|
|
assert(arena != NULL);
|
|
|
|
return ehooks_default_alloc_impl(tsdn, new_addr, size,
|
|
ALIGNMENT_CEILING(alignment, PAGE), zero, commit,
|
|
arena_ind_get(arena));
|
|
}
|
|
|
|
bool
|
|
ehooks_default_dalloc_impl(void *addr, size_t size) {
|
|
if (!have_dss || !extent_in_dss(addr)) {
|
|
return extent_dalloc_mmap(addr, size);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
ehooks_default_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size,
|
|
bool committed, unsigned arena_ind) {
|
|
return ehooks_default_dalloc_impl(addr, size);
|
|
}
|
|
|
|
void
|
|
ehooks_default_destroy_impl(void *addr, size_t size) {
|
|
if (!have_dss || !extent_in_dss(addr)) {
|
|
pages_unmap(addr, size);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ehooks_default_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size,
|
|
bool committed, unsigned arena_ind) {
|
|
ehooks_default_destroy_impl(addr, size);
|
|
}
|
|
|
|
bool
|
|
ehooks_default_commit_impl(void *addr, size_t offset, size_t length) {
|
|
return pages_commit((void *)((uintptr_t)addr + (uintptr_t)offset),
|
|
length);
|
|
}
|
|
|
|
static bool
|
|
ehooks_default_commit(extent_hooks_t *extent_hooks, void *addr, size_t size,
|
|
size_t offset, size_t length, unsigned arena_ind) {
|
|
return ehooks_default_commit_impl(addr, offset, length);
|
|
}
|
|
|
|
bool
|
|
ehooks_default_decommit_impl(void *addr, size_t offset, size_t length) {
|
|
return pages_decommit((void *)((uintptr_t)addr + (uintptr_t)offset),
|
|
length);
|
|
}
|
|
|
|
static bool
|
|
ehooks_default_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size,
|
|
size_t offset, size_t length, unsigned arena_ind) {
|
|
return ehooks_default_decommit_impl(addr, offset, length);
|
|
}
|
|
|
|
#ifdef PAGES_CAN_PURGE_LAZY
|
|
bool
|
|
ehooks_default_purge_lazy_impl(void *addr, size_t offset, size_t length) {
|
|
return pages_purge_lazy((void *)((uintptr_t)addr + (uintptr_t)offset),
|
|
length);
|
|
}
|
|
|
|
static bool
|
|
ehooks_default_purge_lazy(extent_hooks_t *extent_hooks, void *addr, size_t size,
|
|
size_t offset, size_t length, unsigned arena_ind) {
|
|
assert(addr != NULL);
|
|
assert((offset & PAGE_MASK) == 0);
|
|
assert(length != 0);
|
|
assert((length & PAGE_MASK) == 0);
|
|
return ehooks_default_purge_lazy_impl(addr, offset, length);
|
|
}
|
|
#endif
|
|
|
|
#ifdef PAGES_CAN_PURGE_FORCED
|
|
bool
|
|
ehooks_default_purge_forced_impl(void *addr, size_t offset, size_t length) {
|
|
return pages_purge_forced((void *)((uintptr_t)addr +
|
|
(uintptr_t)offset), length);
|
|
}
|
|
|
|
static bool
|
|
ehooks_default_purge_forced(extent_hooks_t *extent_hooks, void *addr,
|
|
size_t size, size_t offset, size_t length, unsigned arena_ind) {
|
|
assert(addr != NULL);
|
|
assert((offset & PAGE_MASK) == 0);
|
|
assert(length != 0);
|
|
assert((length & PAGE_MASK) == 0);
|
|
return ehooks_default_purge_forced_impl(addr, offset, length);
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
ehooks_default_split_impl() {
|
|
if (!maps_coalesce) {
|
|
/*
|
|
* Without retain, only whole regions can be purged (required by
|
|
* MEM_RELEASE on Windows) -- therefore disallow splitting. See
|
|
* comments in extent_head_no_merge().
|
|
*/
|
|
return !opt_retain;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
ehooks_default_split(extent_hooks_t *extent_hooks, void *addr, size_t size,
|
|
size_t size_a, size_t size_b, bool committed, unsigned arena_ind) {
|
|
return ehooks_default_split_impl();
|
|
}
|
|
|
|
bool
|
|
ehooks_default_merge_impl(tsdn_t *tsdn, void *addr_a, void *addr_b) {
|
|
assert(addr_a < addr_b);
|
|
/*
|
|
* For non-DSS cases --
|
|
* a) W/o maps_coalesce, merge is not always allowed (Windows):
|
|
* 1) w/o retain, never merge (first branch below).
|
|
* 2) with retain, only merge extents from the same VirtualAlloc
|
|
* region (in which case MEM_DECOMMIT is utilized for purging).
|
|
*
|
|
* b) With maps_coalesce, it's always possible to merge.
|
|
* 1) w/o retain, always allow merge (only about dirty / muzzy).
|
|
* 2) with retain, to preserve the SN / first-fit, merge is still
|
|
* disallowed if b is a head extent, i.e. no merging across
|
|
* different mmap regions.
|
|
*
|
|
* a2) and b2) are implemented in emap_try_acquire_edata_neighbor, and
|
|
* sanity checked in the second branch below.
|
|
*/
|
|
if (!maps_coalesce && !opt_retain) {
|
|
return true;
|
|
}
|
|
if (config_debug) {
|
|
edata_t *a = emap_edata_lookup(tsdn, &arena_emap_global,
|
|
addr_a);
|
|
bool head_a = edata_is_head_get(a);
|
|
edata_t *b = emap_edata_lookup(tsdn, &arena_emap_global,
|
|
addr_b);
|
|
bool head_b = edata_is_head_get(b);
|
|
emap_assert_mapped(tsdn, &arena_emap_global, a);
|
|
emap_assert_mapped(tsdn, &arena_emap_global, b);
|
|
assert(edata_neighbor_head_state_mergeable(head_a, head_b,
|
|
/* forward */ true));
|
|
}
|
|
if (have_dss && !extent_dss_mergeable(addr_a, addr_b)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
ehooks_default_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a,
|
|
void *addr_b, size_t size_b, bool committed, unsigned arena_ind) {
|
|
tsdn_t *tsdn = tsdn_fetch();
|
|
|
|
return ehooks_default_merge_impl(tsdn, addr_a, addr_b);
|
|
}
|
|
|
|
void
|
|
ehooks_default_zero_impl(void *addr, size_t size) {
|
|
/*
|
|
* By default, we try to zero out memory using OS-provided demand-zeroed
|
|
* pages. If the user has specifically requested hugepages, though, we
|
|
* don't want to purge in the middle of a hugepage (which would break it
|
|
* up), so we act conservatively and use memset.
|
|
*/
|
|
bool needs_memset = true;
|
|
if (opt_thp != thp_mode_always) {
|
|
needs_memset = pages_purge_forced(addr, size);
|
|
}
|
|
if (needs_memset) {
|
|
memset(addr, 0, size);
|
|
}
|
|
}
|
|
|
|
const extent_hooks_t ehooks_default_extent_hooks = {
|
|
ehooks_default_alloc,
|
|
ehooks_default_dalloc,
|
|
ehooks_default_destroy,
|
|
ehooks_default_commit,
|
|
ehooks_default_decommit,
|
|
#ifdef PAGES_CAN_PURGE_LAZY
|
|
ehooks_default_purge_lazy,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
#ifdef PAGES_CAN_PURGE_FORCED
|
|
ehooks_default_purge_forced,
|
|
#else
|
|
NULL,
|
|
#endif
|
|
ehooks_default_split,
|
|
ehooks_default_merge
|
|
};
|