#include "jemalloc/internal/jemalloc_preamble.h"

#include "jemalloc/internal/batcher.h"

#include "jemalloc/internal/assert.h"
#include "jemalloc/internal/atomic.h"

void
batcher_init(batcher_t *batcher, size_t nelems_max) {
	atomic_store_zu(&batcher->nelems, 0, ATOMIC_RELAXED);
	batcher->nelems_max = nelems_max;
	batcher->npushes = 0;
	malloc_mutex_init(&batcher->mtx, "batcher", WITNESS_RANK_BATCHER,
	    malloc_mutex_rank_exclusive);
}

/*
 * Returns an index (into some user-owned array) to use for pushing, or
 * BATCHER_NO_IDX if no index is free.
 */
size_t
batcher_push_begin(tsdn_t *tsdn, batcher_t *batcher, size_t elems_to_push) {
	assert(elems_to_push > 0);
	size_t nelems_guess = atomic_load_zu(&batcher->nelems, ATOMIC_RELAXED);
	if (nelems_guess + elems_to_push > batcher->nelems_max) {
		return BATCHER_NO_IDX;
	}
	malloc_mutex_lock(tsdn, &batcher->mtx);
	size_t nelems = atomic_load_zu(&batcher->nelems, ATOMIC_RELAXED);
	if (nelems + elems_to_push > batcher->nelems_max) {
		malloc_mutex_unlock(tsdn, &batcher->mtx);
		return BATCHER_NO_IDX;
	}
	assert(elems_to_push <= batcher->nelems_max - nelems);
	/*
	 * We update nelems at push time (instead of during pop) so that other
	 * racing accesses of the batcher can fail fast instead of trying to
	 * acquire a mutex only to discover that there's no space for them.
	 */
	atomic_store_zu(
	    &batcher->nelems, nelems + elems_to_push, ATOMIC_RELAXED);
	batcher->npushes++;
	return nelems;
}

size_t
batcher_pop_get_pushes(tsdn_t *tsdn, batcher_t *batcher) {
	malloc_mutex_assert_owner(tsdn, &batcher->mtx);
	size_t npushes = batcher->npushes;
	batcher->npushes = 0;
	return npushes;
}

void
batcher_push_end(tsdn_t *tsdn, batcher_t *batcher) {
	malloc_mutex_assert_owner(tsdn, &batcher->mtx);
	assert(atomic_load_zu(&batcher->nelems, ATOMIC_RELAXED) > 0);
	malloc_mutex_unlock(tsdn, &batcher->mtx);
}

size_t
batcher_pop_begin(tsdn_t *tsdn, batcher_t *batcher) {
	size_t nelems_guess = atomic_load_zu(&batcher->nelems, ATOMIC_RELAXED);
	assert(nelems_guess <= batcher->nelems_max);
	if (nelems_guess == 0) {
		return BATCHER_NO_IDX;
	}
	malloc_mutex_lock(tsdn, &batcher->mtx);
	size_t nelems = atomic_load_zu(&batcher->nelems, ATOMIC_RELAXED);
	assert(nelems <= batcher->nelems_max);
	if (nelems == 0) {
		malloc_mutex_unlock(tsdn, &batcher->mtx);
		return BATCHER_NO_IDX;
	}
	atomic_store_zu(&batcher->nelems, 0, ATOMIC_RELAXED);
	return nelems;
}

void
batcher_pop_end(tsdn_t *tsdn, batcher_t *batcher) {
	assert(atomic_load_zu(&batcher->nelems, ATOMIC_RELAXED) == 0);
	malloc_mutex_unlock(tsdn, &batcher->mtx);
}

void
batcher_prefork(tsdn_t *tsdn, batcher_t *batcher) {
	malloc_mutex_prefork(tsdn, &batcher->mtx);
}

void
batcher_postfork_parent(tsdn_t *tsdn, batcher_t *batcher) {
	malloc_mutex_postfork_parent(tsdn, &batcher->mtx);
}

void
batcher_postfork_child(tsdn_t *tsdn, batcher_t *batcher) {
	malloc_mutex_postfork_child(tsdn, &batcher->mtx);
}
