/*
 * \brief  Slab allocator implementation
 * \author Norman Feske
 * \date   2006-05-16
 */

/*
 * Copyright (C) 2006-2013 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/slab.h>
#include <util/misc_math.h>

using namespace Genode;


/****************
 ** Slab block **
 ****************/

/**
 * Placement operator - tool for directly calling a constructor
 */
inline void *operator new(size_t, void *at) { return at; }


void Slab_block::slab(Slab *slab)
{
	_slab  = slab;
	_avail = _slab->num_elem();
	next   = prev = 0;

	for (unsigned i = 0; i < _avail; i++)
		state(i, FREE);
}


Slab_entry *Slab_block::slab_entry(int idx)
{
	/*
	 * The slab slots start after the state array that consists
	 * of 'num_elem' bytes. We align the first slot to a four-aligned
	 * address.
	 */
	return (Slab_entry *)&_data[align_addr(_slab->num_elem(), 2)
	                            + _slab->entry_size()*idx];
}


int Slab_block::slab_entry_idx(Slab_entry *e) {
	return ((addr_t)e - (addr_t)slab_entry(0))/_slab->entry_size(); }


void *Slab_block::alloc()
{
	size_t num_elem = _slab->num_elem();
	for (unsigned i = 0; i < num_elem; i++)
		if (state(i) == FREE) {
			state(i, USED);
			Slab_entry *e = slab_entry(i);
			e->occupy(this);
			return e->addr();
		}
	return 0;
}


Slab_entry *Slab_block::first_used_entry()
{
	size_t num_elem = _slab->num_elem();
	for (unsigned i = 0; i < num_elem; i++)
		if (state(i) == USED)
			return slab_entry(i);
	return 0;
}


void Slab_block::inc_avail(Slab_entry *e)
{
	/* mark slab entry as free */
	int idx = slab_entry_idx(e);
	state(idx, FREE);
	_avail++;

	/* search previous block with higher avail value than this' */
	Slab_block *at = prev;

	while (at && (at->avail() < _avail))
		at = at->prev;

	/*
	 * If we already are the first block or our avail value is lower than the
	 * previous block, do not reposition the block in the list.
	 */
	if (prev == 0 || at == prev)
		return;

	/* reposition block in list after block with higher avail value */
	_slab->remove_sb(this);
	_slab->insert_sb(this, at);
}


void Slab_block::dec_avail()
{
	_avail--;

	/* search subsequent block with lower avail value than this' */
	Slab_block *at = this;

	while (at->next && at->next->avail() > _avail)
		at = at->next;

	if (at == this) return;

	_slab->remove_sb(this);
	_slab->insert_sb(this, at);
}


/**********
 ** Slab **
 **********/

Slab::Slab(size_t slab_size, size_t block_size, Slab_block *initial_sb,
                                                Allocator *backing_store)
: _slab_size(slab_size),
  _block_size(block_size),
  _first_sb(initial_sb),
  _initial_sb(initial_sb),
  _alloc_state(false),
  _backing_store(backing_store)
{
	/*
	 * Calculate number of entries per slab block.
	 *
	 * The 'sizeof(umword_t)' is for the alignment of the first slab entry.
	 * The 1 is for one byte state entry.
	 */
	_num_elem = (_block_size - sizeof(Slab_block) - sizeof(umword_t))
	          / (entry_size() + 1);

	/* if no initial slab block was specified, try to get one */
	if (!_first_sb && _backing_store)
		_first_sb = _new_slab_block();

	/* init first slab block */
	if (_first_sb)
		_first_sb->slab(this);
}


Slab::~Slab()
{
	/* free backing store */
	while (_first_sb) {
		Slab_block *sb = _first_sb;
		remove_sb(_first_sb);

		/*
		 * Only free slab blocks that we allocated. This is not the case for
		 * the '_initial_sb' that we got as constructor argument.
		 */
		if (_backing_store && (sb != _initial_sb))
			_backing_store->free(sb, _block_size);
	}
}


Slab_block *Slab::_new_slab_block()
{
	void *sb = 0;
	if (!_backing_store || !_backing_store->alloc(_block_size, &sb))
		return 0;

	/* call constructor by using the placement new operator */
	return new(sb) Slab_block(this);
}


void Slab::remove_sb(Slab_block *sb)
{
	Slab_block *prev = sb->prev;
	Slab_block *next = sb->next;

	if (prev) prev->next = next;
	if (next) next->prev = prev;

	if (_first_sb == sb)
		_first_sb =  next;

	sb->prev = sb->next = 0;
}


void Slab::insert_sb(Slab_block *sb, Slab_block *at)
{
	/* determine next-pointer where to assign the current sb */
	Slab_block **nextptr_to_sb = at ? &at->next : &_first_sb;

	/* insert current sb */
	sb->next = *nextptr_to_sb;
	*nextptr_to_sb = sb;

	/* update prev-pointer or succeeding block */
	if (sb->next)
		sb->next->prev = sb;

	sb->prev = at;
}


bool Slab::num_free_entries_higher_than(int n)
{
	int cnt = 0;

	for (Slab_block *b = _first_sb; b && b->avail() > 0; b = b->next) {
		cnt += b->avail();
		if (cnt > n)
			return true;
	}
	return false;
}


bool Slab::alloc(size_t size, void **out_addr)
{
	/* sanity check if first slab block is gone */
	if (!_first_sb) return false;

	/*
	 * If we run out of slab, we need to allocate a new slab block. For the
	 * special case that this block is allocated using the allocator that by
	 * itself uses the slab allocator, such an allocation could cause up to
	 * three new slab_entry allocations. So we need to ensure to allocate the
	 * new slab block early enough - that is if there are only three free slab
	 * entries left.
	 */
	if (_backing_store && !num_free_entries_higher_than(3) && !_alloc_state) {

		/* allocate new block for slab */
		_alloc_state = true;
		Slab_block *sb = _new_slab_block();
		_alloc_state = false;

		if (!sb) return false;

		/*
		 * The new block has the maximum number of available slots and
		 * so we can insert it at the beginning of the sorted block
		 * list.
		 */
		insert_sb(sb);
	}

	*out_addr = _first_sb->alloc();
	return *out_addr == 0 ? false : true;
}


void Slab::free(void *addr)
{
	Slab_entry *e = addr ? Slab_entry::slab_entry(addr) : 0;

	if (e) e->free();
}


void *Slab::first_used_elem()
{
	for (Slab_block *b = _first_sb; b; b = b->next) {

		/* skip completely free slab blocks */
		if (b->avail() == _num_elem)
			continue;

		/* found a block with used elements - return address of the first one */
		Slab_entry *e = b->first_used_entry();
		if (e) return e->addr();
	}
	return 0;
}


size_t Slab::consumed()
{
	/* count number of slab blocks */
	unsigned sb_cnt = 0;
	for (Slab_block *sb = _first_sb; sb; sb = sb->next)
		sb_cnt++;

	return sb_cnt * _block_size;
}
