/* SCE CONFIDENTIAL
 PlayStation(R)Vita Programmer Tool Runtime Library Release 03.000.061
 * Copyright (C) 2012 Sony Computer Entertainment Inc.
 * All Rights Reserved.
 */

// AP - this is a modified version of the basic sony memory overide functions. 
// It was found from profiling that threads were stalling far too much waiting for their turn to malloc/free
// This manager will keep memory bound to a thread once allocated so it can be reused by the same thread later.
// It's only interested in chunks up to 1036 bytes in size, anything greater is not retained
// Todo : I may need to put in a memory flusher if I find threads holding onto memory too much.

#include <stdlib.h>
#include <mspace.h>
#include <kernel.h>
//#include <libdbg.h>
#include <assert.h>
#define HEAP_SIZE (164 * 1024 * 1024)
#define HEAP_ERROR1 1
#define HEAP_ERROR2 2
#define HEAP_ERROR3 3

static SceUID s_heapUid;
static mspace s_mspace;

void user_malloc_init(void);
void user_malloc_finalize(void);
void *user_malloc(size_t size);
void user_free(void *ptr);
void *user_calloc(size_t nelem, size_t size);
void *user_realloc(void *ptr, size_t size);
void *user_memalign(size_t boundary, size_t size);
void *user_reallocalign(void *ptr, size_t size, size_t boundary);
int user_malloc_stats(struct malloc_managed_size *mmsize);
int user_malloc_stats_fast(struct malloc_managed_size *mmsize);
size_t user_malloc_usable_size(void *ptr);

// this is our basic node for managing stacks
typedef struct Block
{
	void* Memory;
	struct Block *Next;
} Block;

#define MaxRetainedBytes 1036
#define Malloc_BlocksMemorySize 1024
typedef struct
{
	Block **Malloc_MemoryPool;		// this is an array of available memory allocations up to 1036 bytes in size
	Block *Malloc_Blocks;			// this is a stack of available block nodes used to store memory chunks
	Block *Malloc_BlocksMemory[Malloc_BlocksMemorySize];		// this is a pool of block nodes allocated in large chunks (256 blocks at a time)
	int Malloc_BlocksAlloced;			// this shows how many block node chunks have been allocated in Malloc_BlocksMemory
} SThreadStorage;

__thread SThreadStorage *Malloc_ThreadStorage = NULL;

/**E Replace _malloc_init function. */
/**J _malloc_init 関数と置き換わる */
void user_malloc_init(void)
{
	int res;
	void *base = NULL;

	/**E Allocate a memory block from the kernel */
	/**J カーネルからメモリブロックを確保する */
	s_heapUid = sceKernelAllocMemBlock("UserAllocator", SCE_KERNEL_MEMBLOCK_TYPE_USER_RWDATA, HEAP_SIZE, SCE_NULL);
	if (s_heapUid < SCE_OK) {
		/**E Error handling */
		/**J エラー処理 */
		sceLibcSetHeapInitError(HEAP_ERROR1);
	} else {
		/**E Obtain the address of the allocated memory block */
		/**J 確保したメモリブロックのアドレスを取得する */
		res = sceKernelGetMemBlockBase(s_heapUid, &base);
		if (res < SCE_OK) {
			/**E Error handling */
			/**J エラー処理 */
			sceLibcSetHeapInitError(HEAP_ERROR2);
		} else {
			/**E Generate mspace */
			/**J mspace を生成する */
			s_mspace = mspace_create(base, HEAP_SIZE);
			if (s_mspace == NULL) {
				/**E Error handling */
				/**J エラー処理 */
				sceLibcSetHeapInitError(HEAP_ERROR3);
			}
		}
	}
}

/**E Replace _malloc_finalize function. */
/**J _malloc_finalize 関数と置き換わる */
void user_malloc_finalize(void)
{
	int res;

	if (s_mspace != NULL) {
		/**E Free mspace */
		/**J mspace を解放する */
		res = mspace_destroy(s_mspace);
		if (res != 0) {
			/**E Error handling */
			/**J エラー処理 */
			__breakpoint(0);
		}
		s_mspace = NULL;
	}

	if (SCE_OK <= s_heapUid) {
		/**E Free the memory block */
		/**J メモリブロックを解放する */
#if 0
		res = sceKernelFreeMemBlock(s_heapUid);
		if (res < SCE_OK) {
			/**E Error handling */
			/**J エラー処理 */
			__breakpoint(0);
		}
#endif
	}
}

// before a thread can use the memory system it should register itself
void user_registerthread()
{
	Malloc_ThreadStorage = mspace_malloc(s_mspace, sizeof(SThreadStorage));
	Malloc_ThreadStorage->Malloc_Blocks = NULL;
	Malloc_ThreadStorage->Malloc_BlocksAlloced = 0;
	Malloc_ThreadStorage->Malloc_MemoryPool = NULL;
}

// before a thread is destroyed make sure we free any space it might be holding on to
void user_removethread()
{
	SThreadStorage *psStorage = Malloc_ThreadStorage;
	if( psStorage )
	{
		if( psStorage->Malloc_MemoryPool )
		{
			for( int j = 0;j < 1037;j += 1 )
			{
				Block *OldBlock = psStorage->Malloc_MemoryPool[j];
				while( OldBlock )
				{
					mspace_free(s_mspace, OldBlock->Memory);
					OldBlock = OldBlock->Next;
				}
			}

			mspace_free(s_mspace, psStorage->Malloc_MemoryPool);
		}

		for( int j = 0;j < psStorage->Malloc_BlocksAlloced;j += 1 )
		{
			free(psStorage->Malloc_BlocksMemory[j]);
		}

		mspace_free(s_mspace, psStorage);
		Malloc_ThreadStorage = NULL;
	}
}

/**E Replace malloc function. */
/**J malloc 関数と置き換わる */
void *user_malloc(size_t size)
{
	void *p = NULL;

	SThreadStorage *psStorage = Malloc_ThreadStorage;
	if( psStorage )
	{
		// is this the first time we've malloced
		if( psStorage->Malloc_MemoryPool == NULL )
		{
			// create an array of pointers to Block nodes, one pointer for each memory bytes size up to 1036
			psStorage->Malloc_MemoryPool = mspace_malloc(s_mspace, (MaxRetainedBytes+1) * 4);
			for( int i = 0;i < (MaxRetainedBytes+1);i += 1 )
			{
				psStorage->Malloc_MemoryPool[i] = NULL;
			}
		}

		// are we interested in retaining this size of memory (less 4 bytes to store the associated thread ID)
		if( size < (MaxRetainedBytes-4) )
		{
			// add on space for the thread ID
			size += 4;

			// round to the nearest malloc boundary. This is what happens internally in the malloc library
			if( size <= 12 )
				size = 12;
			else
				size = ((size - 12) & 0xfffffff0) + 16 + 12;

			// do we have any memory of this size retained
			if( psStorage->Malloc_MemoryPool[size] )
			{
				// pop it from the retained pool
				Block * OldBlock = psStorage->Malloc_MemoryPool[size];
				psStorage->Malloc_MemoryPool[size] = OldBlock->Next;

				p = OldBlock->Memory;

				// push the block storage onto the stack
				OldBlock->Next = psStorage->Malloc_Blocks;
				psStorage->Malloc_Blocks = OldBlock;
			}
			else
			{
				// create some new memory
				p = mspace_malloc(s_mspace, size);

				// store the thread ID in the last 4 bytes
				unsigned int threadID = sceKernelGetThreadId();
				unsigned int *ThreadAddr = (unsigned int *) ((unsigned char*) p + (size - 4));
				ThreadAddr[0] = threadID;
			}
		}
		else
		{
			p = mspace_malloc(s_mspace, size);
		}
	}
	else
	{
		p = mspace_malloc(s_mspace, size);
	}

//	SCE_DBG_LOG_TRACE("Called malloc(%u)", size);
	return p;
}

/**E Replace free function. */
/**J free 関数と置き換わる */
void user_free(void *ptr)
{
	if( !ptr || !s_mspace )
	{
		return;
	}

	SThreadStorage *psStorage = Malloc_ThreadStorage;
	if( psStorage )
	{
		// calc the size of this chunk rounding to a valid size
		unsigned int size = ((unsigned int*)ptr)[-1] - 7;
		unsigned int RoundedSize = (size & 0xfffffff0) + 12;
	
		if( RoundedSize < (MaxRetainedBytes+1) )
		{
			// grab the thread ID from the last 4 bytes
			unsigned int *ThreadAddr = (unsigned int *) ((unsigned char*) ptr + (RoundedSize - 4));

			// did this thread create this memory
			if( sceKernelGetThreadId() != ThreadAddr[0] )
			{
				// don't worry about retaining memory allocated from a different thread. too much mucking about
				mspace_free(s_mspace, ptr);
				return;
			}

			// we need a block node to retain the memory on our stack. do we have any block nodes available
			if( psStorage->Malloc_Blocks == NULL )
			{
				if( psStorage->Malloc_BlocksAlloced == Malloc_BlocksMemorySize ) 
				{
					// Max blocks allocated
					// The worst case for this running out is when the player has explored a large amount of the map in a single session and then quits. All the allocations
					// are suddenly freed up so we need lots of space to store them.
					printf("oh nos, max blocks allocated. increase Malloc_BlocksMemorySize");
					assert(0);
				}

				// allocate some more block space in a large chunk
				int chunkSize = 256;
				psStorage->Malloc_BlocksMemory[psStorage->Malloc_BlocksAlloced] = mspace_malloc(s_mspace, chunkSize * sizeof(Block));

				// push all the new blocks onto the pool
				for(int i = 0;i < chunkSize;i += 1 )
				{
					Block *NewBlock = &psStorage->Malloc_BlocksMemory[psStorage->Malloc_BlocksAlloced][i];
					NewBlock->Next = psStorage->Malloc_Blocks;
					psStorage->Malloc_Blocks = NewBlock;
				}

				psStorage->Malloc_BlocksAlloced++;
			}

			// pop a block node off the stack
			Block *NewBlock = psStorage->Malloc_Blocks;
			psStorage->Malloc_Blocks = NewBlock->Next;

			// set up the block data and retain it to the correct slot. We can now reuse this memory on the next malloc
			NewBlock->Memory = ptr;
			NewBlock->Next = psStorage->Malloc_MemoryPool[RoundedSize];
			psStorage->Malloc_MemoryPool[RoundedSize] = NewBlock;
		}
		else
		{
			mspace_free(s_mspace, ptr);
		}
	}
	else
	{
		mspace_free(s_mspace, ptr);
	}
}

/**E Replace calloc function. */
/**J calloc 関数と置き換わる */
void *user_calloc(size_t nelem, size_t size)
{
//	SCE_DBG_LOG_TRACE("Called calloc(%u, %u)", nelem, size);
	return mspace_calloc(s_mspace, nelem, size);
}

/**E Replace realloc function. */
/**J realloc 関数と置き換わる */
void *user_realloc(void *ptr, size_t size)
{
	void* p = NULL;

	if( Malloc_ThreadStorage )
	{
		// make sure we use malloc/memcpy/free instead of realloc
		p = user_malloc(size);

		unsigned int OldSize;
		unsigned int OldRoundedSize;
		if( ptr )
		{
			// calc the size of this chunk rounding to a valid size
			OldSize = ((unsigned int*)ptr)[-1] - 7;
			OldRoundedSize = (OldSize & 0xfffffff0) + 12;
			memcpy(p, ptr, OldRoundedSize);
			user_free(ptr);
		}
	}
	else
	{
		p = mspace_realloc(s_mspace, ptr, size);
	}

	return p;

//	SCE_DBG_LOG_TRACE("Called realloc(%p, %u)", ptr, size);
//	return mspace_realloc(s_mspace, ptr, size);
}

/**E Replace memalign function. */
/**J memalign 関数と置き換わる */
void *user_memalign(size_t boundary, size_t size)
{
//	SCE_DBG_LOG_TRACE("Called memalign(%u, %u)", boundary, size);
	return mspace_memalign(s_mspace, boundary, size);
}

/**E Replace reallocalign function. */
/**J reallocalign 関数と置き換わる */
void *user_reallocalign(void *ptr, size_t size, size_t boundary)
{
//	SCE_DBG_LOG_TRACE("Called reallocalign(%p, %u, %u)", ptr, size, boundary);
	return mspace_reallocalign(s_mspace, ptr, size, boundary);
}

/**E Replace malloc_stats function. */
/**J malloc_stats 関数と置き換わる */
int user_malloc_stats(struct malloc_managed_size *mmsize)
{
//	SCE_DBG_LOG_TRACE("Called malloc_stats");
	return mspace_malloc_stats(s_mspace, mmsize);
}

/**E Replace malloc_stats_fast function. */
/**J malloc_stata_fast 関数と置き換わる */
int user_malloc_stats_fast(struct malloc_managed_size *mmsize)
{
//	SCE_DBG_LOG_TRACE("Called malloc_stats_fast");
	return mspace_malloc_stats_fast(s_mspace, mmsize);
}

/**E Replace malloc_usable_size function. */
/**J malloc_usable_size 関数と置き換わる */
size_t user_malloc_usable_size(void *ptr)
{
//	SCE_DBG_LOG_TRACE("Called malloc_usable_size");
	return mspace_malloc_usable_size(ptr);
}

