#include "jemalloc/internal/jemalloc_preamble.h"
#include "jemalloc/internal/jemalloc_internal_includes.h"

#include "jemalloc/internal/bit_util.h"
#include "jemalloc/internal/cache_bin.h"
#include "jemalloc/internal/safety_check.h"

const uintptr_t disabled_bin = JUNK_ADDR;

void
cache_bin_info_init(cache_bin_info_t *info, cache_bin_sz_t ncached_max) {
	assert(ncached_max <= CACHE_BIN_NCACHED_MAX);
	size_t stack_size = (size_t)ncached_max * sizeof(void *);
	assert(stack_size < ((size_t)1 << (sizeof(cache_bin_sz_t) * 8)));
	info->ncached_max = (cache_bin_sz_t)ncached_max;
}

bool
cache_bin_stack_use_thp(void) {
	/*
	 * If metadata_thp is enabled, allocating tcache stack from the base
	 * allocator for efficiency gains.  The downside, however, is that base
	 * allocator never purges freed memory, and may cache a fair amount of
	 * memory after many threads are terminated and not reused.
	 */
	return metadata_thp_enabled();
}

void
cache_bin_info_compute_alloc(const cache_bin_info_t *infos, szind_t ninfos,
    size_t *size, size_t *alignment) {
	/* For the total bin stack region (per tcache), reserve 2 more slots so
	 * that
	 * 1) the empty position can be safely read on the fast path before
	 *    checking "is_empty"; and
	 * 2) the cur_ptr can go beyond the empty position by 1 step safely on
	 * the fast path (i.e. no overflow).
	 */
	*size = sizeof(void *) * 2;
	for (szind_t i = 0; i < ninfos; i++) {
		*size += infos[i].ncached_max * sizeof(void *);
	}

	/*
	 * When not using THP, align to at least PAGE, to minimize the # of TLBs
	 * needed by the smaller sizes; also helps if the larger sizes don't get
	 * used at all.
	 */
	*alignment = cache_bin_stack_use_thp() ? QUANTUM : PAGE;
}

void
cache_bin_preincrement(const cache_bin_info_t *infos, szind_t ninfos,
    void *alloc, size_t *cur_offset) {
	if (config_debug) {
		size_t computed_size;
		size_t computed_alignment;

		/* Pointer should be as aligned as we asked for. */
		cache_bin_info_compute_alloc(
		    infos, ninfos, &computed_size, &computed_alignment);
		assert(((uintptr_t)alloc & (computed_alignment - 1)) == 0);
	}

	*(uintptr_t *)((byte_t *)alloc
	    + *cur_offset) = cache_bin_preceding_junk;
	*cur_offset += sizeof(void *);
}

void
cache_bin_postincrement(void *alloc, size_t *cur_offset) {
	*(uintptr_t *)((byte_t *)alloc + *cur_offset) = cache_bin_trailing_junk;
	*cur_offset += sizeof(void *);
}

void
cache_bin_init(cache_bin_t *bin, const cache_bin_info_t *info, void *alloc,
    size_t *cur_offset) {
	/*
	 * The full_position points to the lowest available space.  Allocations
	 * will access the slots toward higher addresses (for the benefit of
	 * adjacent prefetch).
	 */
	void          *stack_cur = (void *)((byte_t *)alloc + *cur_offset);
	void          *full_position = stack_cur;
	cache_bin_sz_t bin_stack_size = info->ncached_max * sizeof(void *);

	*cur_offset += bin_stack_size;
	void *empty_position = (void *)((byte_t *)alloc + *cur_offset);

	/* Init to the empty position. */
	bin->stack_head = (void **)empty_position;
	bin->low_bits_low_water = (cache_bin_sz_t)(uintptr_t)bin->stack_head;
	bin->low_bits_full = (cache_bin_sz_t)(uintptr_t)full_position;
	bin->low_bits_empty = (cache_bin_sz_t)(uintptr_t)empty_position;
	cache_bin_info_init(&bin->bin_info, info->ncached_max);
	cache_bin_sz_t free_spots = cache_bin_diff(bin, bin->low_bits_full,
	    (cache_bin_sz_t)(uintptr_t)bin->stack_head);
	assert(free_spots == bin_stack_size);
	if (!cache_bin_disabled(bin)) {
		assert(cache_bin_ncached_get_local(bin) == 0);
	}
	assert(cache_bin_empty_position_get(bin) == empty_position);

	assert(bin_stack_size > 0 || empty_position == full_position);
}

void
cache_bin_init_disabled(cache_bin_t *bin, cache_bin_sz_t ncached_max) {
	const void      *fake_stack = cache_bin_disabled_bin_stack();
	size_t           fake_offset = 0;
	cache_bin_info_t fake_info;
	cache_bin_info_init(&fake_info, 0);
	cache_bin_init(bin, &fake_info, (void *)fake_stack, &fake_offset);
	cache_bin_info_init(&bin->bin_info, ncached_max);
	assert(fake_offset == 0);
}
