⛏️ index : haiku.git

/*
 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2008-2010, Axel Dörfler. All Rights Reserved.
 * Copyright 2007, Hugo Santos. All Rights Reserved.
 *
 * Distributed under the terms of the MIT License.
 */


#include "ObjectDepot.h"

#include <algorithm>

#include <interrupts.h>
#include <slab/Slab.h>
#include <smp.h>
#include <util/AutoLock.h>

#include "slab_debug.h"
#include "slab_private.h"
#include "slab_queue.h"


struct DepotMagazine : public slab_queue_link {
			uint16				current_round;
			uint16				round_count;
			void*				rounds[0];

public:
	inline	bool				IsEmpty() const;
	inline	bool				IsFull() const;

	inline	void*				Pop();
	inline	bool				Push(void* object);

#if PARANOID_KERNEL_FREE
			bool				ContainsObject(void* object) const;
#endif
};


#if PARANOID_KERNEL_FREE
struct depot_cpu_store {
	DepotMagazine*	obtain;
	DepotMagazine*	store;
};
#else
struct depot_cpu_store {
	DepotMagazine*	loaded;
	DepotMagazine*	previous;
};
#endif


RANGE_MARKER_FUNCTION_BEGIN(SlabObjectDepot)


bool
DepotMagazine::IsEmpty() const
{
	return current_round == 0;
}


bool
DepotMagazine::IsFull() const
{
	return current_round == round_count;
}


void*
DepotMagazine::Pop()
{
	return rounds[--current_round];
}


bool
DepotMagazine::Push(void* object)
{
	if (IsFull())
		return false;

	rounds[current_round++] = object;
	return true;
}


#if PARANOID_KERNEL_FREE

bool
DepotMagazine::ContainsObject(void* object) const
{
	for (uint16 i = 0; i < current_round; i++) {
		if (rounds[i] == object)
			return true;
	}

	return false;
}

#endif // PARANOID_KERNEL_FREE


// #pragma mark -


static DepotMagazine*
alloc_magazine(object_depot* depot, uint32 flags)
{
	DepotMagazine* magazine = (DepotMagazine*)slab_internal_alloc(
		sizeof(DepotMagazine) + depot->magazine_capacity * sizeof(void*),
		flags);
	if (magazine) {
		magazine->next = NULL;
		magazine->current_round = 0;
		magazine->round_count = depot->magazine_capacity;
	}

	return magazine;
}


static void
free_magazine(DepotMagazine* magazine, uint32 flags)
{
	slab_internal_free(magazine, flags);
}


static void
empty_magazine(object_depot* depot, DepotMagazine* magazine, uint32 flags)
{
	for (uint16 i = 0; i < magazine->current_round; i++)
		depot->return_object(depot, depot->cookie, magazine->rounds[i], flags);
	free_magazine(magazine, flags);
}


static bool
exchange_with_full(object_depot* depot, DepotMagazine*& magazine)
{
	ASSERT(magazine == NULL || magazine->IsEmpty());

	SpinLocker _(depot->inner_lock);

	if (depot->full.head == NULL)
		return false;

	if (magazine != NULL) {
		depot->empty.Push(magazine);
		depot->empty_count++;
	}

	magazine = (DepotMagazine*)depot->full.Pop();
	depot->full_count--;
	return true;
}


static bool
exchange_with_empty(object_depot* depot, DepotMagazine*& magazine,
	DepotMagazine*& freeMagazine)
{
	ASSERT(magazine == NULL || magazine->IsFull());

#if PARANOID_KERNEL_FREE
	if (magazine != NULL) {
		// Reverse the rounds in the magazine so we get FIFO, not LIFO.
		for (int i = 0; i < magazine->current_round / 2; i++)
			std::swap(magazine->rounds[i], magazine->rounds[magazine->current_round - i - 1]);
	}
#endif

	SpinLocker _(depot->inner_lock);

	if (depot->empty.head == NULL)
		return false;

	if (magazine != NULL) {
		if (depot->full_count < depot->max_count) {
			depot->full.Push(magazine);
			depot->full_count++;
			freeMagazine = NULL;
		} else
			freeMagazine = magazine;
	}

	magazine = (DepotMagazine*)depot->empty.Pop();
	depot->empty_count--;
	return true;
}


static void
push_empty_magazine(object_depot* depot, DepotMagazine* magazine)
{
	SpinLocker _(depot->inner_lock);

	depot->empty.Push(magazine);
	depot->empty_count++;
}


static inline depot_cpu_store*
object_depot_cpu(object_depot* depot)
{
	return &depot->stores[smp_get_current_cpu()];
}


// #pragma mark - public API


status_t
object_depot_init(object_depot* depot, size_t capacity, size_t maxCount,
	uint32 flags, void* cookie, void (*return_object)(object_depot* depot,
		void* cookie, void* object, uint32 flags))
{
	depot->full.Init();
	depot->empty.Init();
	depot->full_count = depot->empty_count = 0;
	depot->max_count = maxCount;
	depot->magazine_capacity = capacity;

	rw_lock_init(&depot->outer_lock, "object depot");
	B_INITIALIZE_SPINLOCK(&depot->inner_lock);

	int cpuCount = smp_get_num_cpus();
	depot->stores = (depot_cpu_store*)slab_internal_alloc(
		sizeof(depot_cpu_store) * cpuCount, flags);
	if (depot->stores == NULL) {
		rw_lock_destroy(&depot->outer_lock);
		return B_NO_MEMORY;
	}

	for (int i = 0; i < cpuCount; i++) {
#if PARANOID_KERNEL_FREE
		depot->stores[i].obtain = NULL;
		depot->stores[i].store = NULL;
#else
		depot->stores[i].loaded = NULL;
		depot->stores[i].previous = NULL;
#endif
	}

	depot->cookie = cookie;
	depot->return_object = return_object;

	return B_OK;
}


void
object_depot_destroy(object_depot* depot, uint32 flags)
{
	object_depot_make_empty(depot, flags);

	slab_internal_free(depot->stores, flags);

	rw_lock_destroy(&depot->outer_lock);
}


void*
object_depot_obtain(object_depot* depot)
{
	ReadLocker readLocker(depot->outer_lock);
	InterruptsLocker interruptsLocker;

	depot_cpu_store* store = object_depot_cpu(depot);

#if PARANOID_KERNEL_FREE
	// When paranoid free is enabled, we want to defer object reuse,
	// instead of reusing as rapidly as possible. Thus we always exchange
	// full and empty magazines with the depot.

	if (store->obtain == NULL || store->obtain->IsEmpty()) {
		if (!exchange_with_full(depot, store->obtain))
			return NULL;
	}

	return store->obtain->Pop();
#else
	// To better understand both the Alloc() and Free() logic refer to
	// Bonwick's ``Magazines and Vmem'' [in 2001 USENIX proceedings]

	// In a nutshell, we try to get an object from the loaded magazine
	// if it's not empty, or from the previous magazine if it's full
	// and finally from the Slab if the magazine depot has no full magazines.

	if (store->loaded == NULL)
		return NULL;

	while (true) {
		if (!store->loaded->IsEmpty())
			return store->loaded->Pop();

		if (store->previous != NULL
			&& (store->previous->IsFull()
				|| exchange_with_full(depot, store->previous))) {
			std::swap(store->previous, store->loaded);
		} else
			return NULL;
	}
#endif
}


void
object_depot_store(object_depot* depot, void* object, uint32 flags)
{
	ReadLocker readLocker(depot->outer_lock);
	InterruptsLocker interruptsLocker;

	depot_cpu_store* store = object_depot_cpu(depot);

	while (true) {
#if PARANOID_KERNEL_FREE
		if (store->store != NULL && store->store->Push(object))
			return;

		DepotMagazine* freeMagazine = NULL;
		if (exchange_with_empty(depot, store->store, freeMagazine)) {
#else
		// We try to add the object to the loaded magazine if we have one
		// and it's not full, or to the previous one if it is empty. If
		// the magazine depot doesn't provide us with a new empty magazine
		// we return the object directly to the slab.

		if (store->loaded != NULL && store->loaded->Push(object))
			return;

		DepotMagazine* freeMagazine = NULL;
		if ((store->previous != NULL && store->previous->IsEmpty())
			|| exchange_with_empty(depot, store->previous, freeMagazine)) {
			std::swap(store->loaded, store->previous);
#endif
			if (freeMagazine != NULL) {
				// Free the magazine that didn't have space in the list
				interruptsLocker.Unlock();
				readLocker.Unlock();

				empty_magazine(depot, freeMagazine, flags);

				readLocker.Lock();
				interruptsLocker.Lock();

				store = object_depot_cpu(depot);
			}
		} else {
			// allocate a new empty magazine
			interruptsLocker.Unlock();
			readLocker.Unlock();

			DepotMagazine* magazine = alloc_magazine(depot, flags);
			if (magazine == NULL) {
				depot->return_object(depot, depot->cookie, object, flags);
				return;
			}

			readLocker.Lock();
			interruptsLocker.Lock();

			push_empty_magazine(depot, magazine);
			store = object_depot_cpu(depot);
		}
	}
}


void
object_depot_make_empty(object_depot* depot, uint32 flags)
{
	WriteLocker writeLocker(depot->outer_lock);

	// collect the store magazines

	slab_queue storeMagazines;
	storeMagazines.Init();

	int cpuCount = smp_get_num_cpus();
	for (int i = 0; i < cpuCount; i++) {
		depot_cpu_store& store = depot->stores[i];

#if PARANOID_KERNEL_FREE
		if (store.obtain != NULL) {
			storeMagazines.Push(store.obtain);
			store.obtain = NULL;
		}

		if (store.store != NULL) {
			storeMagazines.Push(store.store);
			store.store = NULL;
		}
#else
		if (store.loaded != NULL) {
			storeMagazines.Push(store.loaded);
			store.loaded = NULL;
		}

		if (store.previous != NULL) {
			storeMagazines.Push(store.previous);
			store.previous = NULL;
		}
#endif
	}

	// detach the depot's full and empty magazines

	slab_queue fullMagazines = depot->full;
	depot->full.Init();

	slab_queue emptyMagazines = depot->empty;
	depot->empty.Init();

	writeLocker.Unlock();

	// free all magazines

	while (storeMagazines.head != NULL)
		empty_magazine(depot, (DepotMagazine*)storeMagazines.Pop(), flags);

	while (fullMagazines.head != NULL)
		empty_magazine(depot, (DepotMagazine*)fullMagazines.Pop(), flags);

	while (emptyMagazines.head != NULL)
		free_magazine((DepotMagazine*)emptyMagazines.Pop(), flags);
}


#if PARANOID_KERNEL_FREE

bool
object_depot_contains_object(object_depot* depot, void* object)
{
	WriteLocker writeLocker(depot->outer_lock);

	int cpuCount = smp_get_num_cpus();
	for (int i = 0; i < cpuCount; i++) {
		depot_cpu_store& store = depot->stores[i];

		if (store.obtain != NULL && !store.obtain->IsEmpty()) {
			if (store.obtain->ContainsObject(object))
				return true;
		}

		if (store.store != NULL && !store.store->IsEmpty()) {
			if (store.store->ContainsObject(object))
				return true;
		}
	}

	for (DepotMagazine* magazine = (DepotMagazine*)depot->full.head; magazine != NULL;
			magazine = (DepotMagazine*)magazine->next) {
		if (magazine->ContainsObject(object))
			return true;
	}

	return false;
}

#endif // PARANOID_KERNEL_FREE


// #pragma mark - private kernel API


void
dump_object_depot(object_depot* depot)
{
	kprintf("  full:     %p, count %lu\n", depot->full.head, depot->full_count);
	kprintf("  empty:    %p, count %lu\n", depot->empty.head, depot->empty_count);
	kprintf("  max full: %lu\n", depot->max_count);
	kprintf("  capacity: %lu\n", depot->magazine_capacity);
	kprintf("  stores:\n");

	int cpuCount = smp_get_num_cpus();

#if PARANOID_KERNEL_FREE
	for (int i = 0; i < cpuCount; i++) {
		kprintf("  [%d] obtain:   %p\n", i, depot->stores[i].obtain);
		kprintf("      store:    %p\n", depot->stores[i].store);
	}
#else
	for (int i = 0; i < cpuCount; i++) {
		kprintf("  [%d] loaded:   %p\n", i, depot->stores[i].loaded);
		kprintf("      previous: %p\n", depot->stores[i].previous);
	}
#endif
}


int
dump_object_depot(int argCount, char** args)
{
	if (argCount != 2)
		kprintf("usage: %s [address]\n", args[0]);
	else
		dump_object_depot((object_depot*)parse_expression(args[1]));

	return 0;
}


int
dump_depot_magazine(int argCount, char** args)
{
	if (argCount != 2) {
		kprintf("usage: %s [address]\n", args[0]);
		return 0;
	}

	DepotMagazine* magazine = (DepotMagazine*)parse_expression(args[1]);

	kprintf("next:          %p\n", magazine->next);
	kprintf("current_round: %u\n", magazine->current_round);
	kprintf("round_count:   %u\n", magazine->round_count);

	for (uint16 i = 0; i < magazine->current_round; i++)
		kprintf("  [%i] %p\n", i, magazine->rounds[i]);

	return 0;
}


RANGE_MARKER_FUNCTION_END(SlabObjectDepot)