/*
 * Copyright 2009-2010, Ingo Weinhold, ingo_weinhold@gmx.de
 * Distributed under the terms of the MIT License.
 */
 
 
#include <debug_heap.h>
 
#include <new>
 
#include <util/AutoLock.h>
#include <vm/vm.h>
 
 
#define INITIAL_DEBUG_HEAP_SIZE	B_PAGE_SIZE
 
static char sInitialHeap[INITIAL_DEBUG_HEAP_SIZE] __attribute__ ((aligned (8)));
static void* sHeapBase = sInitialHeap;
static size_t sHeapSize = INITIAL_DEBUG_HEAP_SIZE;
 
const kdebug_alloc_t kdebug_alloc = {};
 
 
struct allocation_header {
	uint32	size : 31;	// size in allocation_header units
	bool	free : 1;
	uint32	previous;
};
 
struct free_entry : allocation_header {
	uint32	previous_free;
	uint32	next_free;
};
 
 
struct DebugAllocPool {
	void Init(void* heap, size_t heapSize)
	{
		fParent = NULL;
		fChild = NULL;
 
		uint32 size = heapSize / 8;
		fBase = (allocation_header*)heap - 1;
		fEnd = size + 1;
		fFirstFree = 0;
		fLastFree = 0;
 
		// add free entry spanning the whole area
		fBase[1].size = size - 1;
		fBase[1].previous = 0;
		_InsertFreeEntry(1);
	}
 
	DebugAllocPool* CreateChildPool()
	{
		// do we already have a child pool?
		if (fChild != NULL)
			return NULL;
 
		// create the pool object
		DebugAllocPool* pool
			= (DebugAllocPool*)Allocate(sizeof(DebugAllocPool));
		if (pool == NULL)
			return NULL;
 
		// do we have enough free space?
		if (fLastFree == 0 || fBase[fLastFree].size < 2) {
			Free(pool);
			return NULL;
		}
 
		allocation_header* header = &fBase[fLastFree];
		_RemoveFreeEntry(fLastFree);
 
		pool->Init(header + 1, header->size * 8);
		pool->fParent = this;
 
		return fChild = pool;
	}
 
	void Destroy()
	{
		if (fParent != NULL) {
			fParent->fChild = NULL;
			fParent->Free(fBase + 1);
		}
	}
 
	DebugAllocPool* Parent() const
	{
		return fParent;
	}
 
	void* Allocate(size_t size)
	{
		size = (size + 7) / 8;
		uint32 index = fFirstFree;
		while (index != 0 && fBase[index].size < size)
			index = ((free_entry*)&fBase[index])->next_free;
 
		if (index == 0)
			return NULL;
 
		_RemoveFreeEntry(index);
 
		// if the entry is big enough, we split it
		if (fBase[index].size - size >= 2) {
			uint32 next = index + 1 + size;
			uint32 nextNext = index + 1 + fBase[index].size;
			fBase[next].size = fBase[index].size - size - 1;
			fBase[next].previous = index;
			fBase[index].size = size;
			_InsertFreeEntry(next);
 
			if (nextNext < fEnd)
				fBase[nextNext].previous = next;
		}
 
		return &fBase[index + 1];
	}
 
	void Free(void* address)
	{
		// check address
		if (((addr_t)address & 7) != 0 || address <= fBase + 1
			|| address >= fBase + fEnd) {
			kprintf("DebugAllocPool::Free(%p): bad address\n", address);
			return;
		}
 
		// get header
		allocation_header* header = (allocation_header*)address - 1;
		uint32 index = header - fBase;
		if (header->free) {
			kprintf("DebugAllocPool::Free(%p): double free\n", address);
			return;
		}
 
		uint32 next = index + 1 + header->size;
 
		// join with previous, if possible
		if (index > 1 && fBase[header->previous].free) {
			uint32 previous = header->previous;
			_RemoveFreeEntry(previous);
 
			fBase[previous].size += 1 + header->size;
			fBase[next].previous = previous;
 
			index = previous;
			header = fBase + index;
		}
 
		// join with next, if possible
		if (next < fEnd && fBase[next].free) {
			_RemoveFreeEntry(next);
 
			header->size += 1 + fBase[next].size;
 
			uint32 nextNext = index + 1 + header->size;
			if (nextNext < fEnd)
				fBase[nextNext].previous = index;
		}
 
		_InsertFreeEntry(index);
	}
 
private:
	void _InsertFreeEntry(uint32 index)
	{
		// find the insertion point -- list is sorted by ascending size
		uint32 size = fBase[index].size;
		uint32 next = fFirstFree;
		while (next != 0 && size > fBase[next].size)
			next = ((free_entry*)&fBase[next])->next_free;
 
		// insert
		uint32 previous;
		if (next != 0) {
			previous = ((free_entry*)&fBase[next])->previous_free;
			((free_entry*)&fBase[next])->previous_free = index;
		} else {
			previous = fLastFree;
			fLastFree = index;
		}
 
		if (previous != 0)
			((free_entry*)&fBase[previous])->next_free = index;
		else
			fFirstFree = index;
 
		((free_entry*)&fBase[index])->previous_free = previous;
		((free_entry*)&fBase[index])->next_free = next;
 
		fBase[index].free = true;
	}
 
	void _RemoveFreeEntry(uint32 index)
	{
		uint32 previous = ((free_entry*)&fBase[index])->previous_free;
		uint32 next = ((free_entry*)&fBase[index])->next_free;
 
		if (previous != 0)
			((free_entry*)&fBase[previous])->next_free = next;
		else
			fFirstFree = next;
 
		if (next != 0)
			((free_entry*)&fBase[next])->previous_free = previous;
		else
			fLastFree = previous;
 
		fBase[index].free = false;
	}
 
private:
	DebugAllocPool*		fParent;
	DebugAllocPool*		fChild;
	allocation_header*	fBase;		// actually base - 1, so that index 0 is
									// invalid
	uint32				fEnd;
	uint32				fFirstFree;
	uint32				fLastFree;
};
 
 
static DebugAllocPool* sCurrentPool;
static DebugAllocPool sInitialPool;
 
 
debug_alloc_pool*
create_debug_alloc_pool()
{
	if (sCurrentPool == NULL) {
		sInitialPool.Init(sHeapBase, sHeapSize);
		sCurrentPool = &sInitialPool;
		return sCurrentPool;
	}
 
	DebugAllocPool* pool = sCurrentPool->CreateChildPool();
	if (pool == NULL)
		return NULL;
 
	sCurrentPool = pool;
	return sCurrentPool;
}
 
 
void
delete_debug_alloc_pool(debug_alloc_pool* pool)
{
	if (pool == NULL || sCurrentPool == NULL)
		return;
 
	// find the pool in the hierarchy
	DebugAllocPool* otherPool = sCurrentPool;
	while (otherPool != NULL && otherPool != pool)
		otherPool = otherPool->Parent();
 
	if (otherPool == NULL)
		return;
 
	// destroy the pool
	sCurrentPool = pool->Parent();
	pool->Destroy();
 
	if (pool != &sInitialPool)
		debug_free(pool);
}
 
 
void*
debug_malloc(size_t size)
{
	if (sCurrentPool == NULL)
		return NULL;
 
	return sCurrentPool->Allocate(size);
}
 
 
void*
debug_calloc(size_t num, size_t size)
{
	size_t allocationSize = num * size;
	void* allocation = debug_malloc(allocationSize);
	if (allocation == NULL)
		return NULL;
 
	memset(allocation, 0, allocationSize);
	return allocation;
}
 
 
void
debug_free(void* address)
{
	if (address != NULL && sCurrentPool != NULL)
		sCurrentPool->Free(address);
}
 
 
void
debug_heap_init()
{
	// create the heap area
	void* base;
	virtual_address_restrictions virtualRestrictions = {};
	virtualRestrictions.address_specification = B_ANY_KERNEL_ADDRESS;
	physical_address_restrictions physicalRestrictions = {};
	area_id area = create_area_etc(B_SYSTEM_TEAM, "kdebug heap", KDEBUG_HEAP,
		B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
		CREATE_AREA_DONT_WAIT, 0, &virtualRestrictions, &physicalRestrictions,
		(void**)&base);
	if (area < 0)
		return;
 
	// switch from the small static buffer to the area
	InterruptsLocker locker;
	sHeapBase = base;
	sHeapSize = KDEBUG_HEAP;
}

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.

V717 It is suspicious to cast object of base class 'allocation_header' to derived class 'free_entry'.