/***************************************************************************

    drccache.h

    Universal dynamic recompiler cache management.

    Copyright Aaron Giles
    Released for general non-commercial use under the MAME license
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#include <stddef.h>
#include "cpuintrf.h"
#include "drccache.h"



/***************************************************************************
    CONSTANTS
***************************************************************************/

/* largest block of code that can be generated at once */
#define CODEGEN_MAX_BYTES		65536

/* minimum alignment, in bytes (must be power of 2) */
#define CACHE_ALIGNMENT			8

/* largest permanent allocation we allow */
#define MAX_PERMANENT_ALLOC		1024

/* size of "near" area at the base of the cache */
#define NEAR_CACHE_SIZE			65536



/***************************************************************************
    MACROS
***************************************************************************/

/* ensure that all memory allocated is aligned to an 8-byte boundary */
#define ALIGN_PTR_UP(p)			((void *)(((FPTR)(p) + (CACHE_ALIGNMENT - 1)) & ~(CACHE_ALIGNMENT - 1)))
#define ALIGN_PTR_DOWN(p)		((void *)((FPTR)(p) & ~(CACHE_ALIGNMENT - 1)))



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* out-of-bounds codegen handlers */
typedef struct _oob_handler oob_handler;
struct _oob_handler
{
	oob_handler *		next;				/* next handler */
	drccache_oob_func	callback;			/* callback function */
	void *				param1;				/* 1st pointer parameter */
	void *				param2;				/* 2nd pointer parameter */
	void *				param3;				/* 3rd pointer parameter */
};


/* a linked list of free items */
typedef struct _free_link free_link;
struct _free_link
{
	free_link *			next;				/* pointer to the next guy */
};


/* cache state */
struct _drccache
{
	/* core parameters */
	drccodeptr			near;				/* pointer to the near part of the cache */
	drccodeptr			neartop;			/* top of the near part of the cache */
	drccodeptr			base;				/* base pointer to the compiler cache */
	drccodeptr			top;				/* current top of cache */
	drccodeptr			end;				/* end of cache memory */
	drccodeptr			codegen;			/* start of generated code */
	size_t				size;				/* size of the cache in bytes */

	/* oob management */
	oob_handler *		ooblist;			/* list of oob handlers */
	oob_handler **		oobtail;			/* pointer to tail oob pointer */

	/* free lists */
	free_link *			free[MAX_PERMANENT_ALLOC / CACHE_ALIGNMENT];
	free_link *			nearfree[MAX_PERMANENT_ALLOC / CACHE_ALIGNMENT];
};



/***************************************************************************
    INITIALIZATION/TEARDOWN
***************************************************************************/

/*-------------------------------------------------
    drccache_alloc - allocate the cache itself
-------------------------------------------------*/

drccache *drccache_alloc(size_t bytes)
{
	drccache cache, *cacheptr;

	assert(bytes >= sizeof(cache) + NEAR_CACHE_SIZE);

	/* build a local structure first */
	memset(&cache, 0, sizeof(cache));
	cache.near = osd_alloc_executable(bytes);
	cache.neartop = cache.near;
	cache.base = cache.near + NEAR_CACHE_SIZE;
	cache.top = cache.base;
	cache.end = cache.near + bytes;
	cache.size = bytes;

	/* now allocate the cache structure itself from that */
	cacheptr = drccache_memory_alloc(&cache, sizeof(cache));
	*cacheptr = cache;

	/* return the allocated result */
	return cacheptr;
}


/*-------------------------------------------------
    drccache_free - free the cache
-------------------------------------------------*/

void drccache_free(drccache *cache)
{
	/* release the memory; this includes the cache object itself */
	osd_free_executable(cache->near, cache->size);
}



/***************************************************************************
    CACHE INFORMATION
***************************************************************************/

/*-------------------------------------------------
    drccache_contains_pointer - return true if a
    pointer is within the cache
-------------------------------------------------*/

int drccache_contains_pointer(drccache *cache, const void *ptr)
{
	return ((const drccodeptr)ptr >= cache->near && (const drccodeptr)ptr < cache->near + cache->size);
}


/*-------------------------------------------------
    drccache_contains_near_pointer - return true
    if a pointer is within the cache
-------------------------------------------------*/

int drccache_contains_near_pointer(drccache *cache, const void *ptr)
{
	return ((const drccodeptr)ptr >= cache->near && (const drccodeptr)ptr < cache->neartop);
}


/*-------------------------------------------------
    drccache_near - return a pointer to the near
    part of the cache
-------------------------------------------------*/

drccodeptr drccache_near(drccache *cache)
{
	return cache->near;
}


/*-------------------------------------------------
    drccache_base - return a pointer to the base
    of the cache
-------------------------------------------------*/

drccodeptr drccache_base(drccache *cache)
{
	return cache->base;
}


/*-------------------------------------------------
    drccache_top - return the current top of the
    cache
-------------------------------------------------*/

drccodeptr drccache_top(drccache *cache)
{
	return cache->top;
}



/***************************************************************************
    MEMORY MANAGEMENT
***************************************************************************/

/*-------------------------------------------------
    drccache_flush - flush the cache contents
-------------------------------------------------*/

void drccache_flush(drccache *cache)
{
	/* can't flush in the middle of codegen */
	assert(cache->codegen == NULL);

	/* just reset the top back to the base and re-seed */
	cache->top = cache->base;
}


/*-------------------------------------------------
    drccache_memory_alloc - allocate permanent
    memory from the cache
-------------------------------------------------*/

void *drccache_memory_alloc(drccache *cache, size_t bytes)
{
	drccodeptr ptr;

	assert(bytes > 0);

	/* pick first from the free list */
	if (bytes < MAX_PERMANENT_ALLOC)
	{
		free_link **linkptr = &cache->free[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
		free_link *link = *linkptr;
		if (link != NULL)
		{
			*linkptr = link->next;
			return link;
		}
	}

	/* if no space, we just fail */
	ptr = ALIGN_PTR_DOWN(cache->end - bytes);
	if (cache->top > ptr)
		return NULL;

	/* otherwise update the end of the cache */
	cache->end = ptr;
	return ptr;
}


/*-------------------------------------------------
    drccache_memory_alloc_near - allocate
    permanent memory from the near part of the
    cache
-------------------------------------------------*/

void *drccache_memory_alloc_near(drccache *cache, size_t bytes)
{
	drccodeptr ptr;

	assert(bytes > 0);

	/* pick first from the free list */
	if (bytes < MAX_PERMANENT_ALLOC)
	{
		free_link **linkptr = &cache->nearfree[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
		free_link *link = *linkptr;
		if (link != NULL)
		{
			*linkptr = link->next;
			return link;
		}
	}

	/* if no space, we just fail */
	ptr = ALIGN_PTR_UP(cache->neartop);
	if (ptr + bytes > cache->base)
		return NULL;

	/* otherwise update the top of the near part of the cache */
	cache->neartop = ptr + bytes;
	return ptr;
}


/*-------------------------------------------------
    drccache_memory_free - release permanent
    memory allocated from the cache
-------------------------------------------------*/

void drccache_memory_free(drccache *cache, void *memory, size_t bytes)
{
	free_link **linkptr;
	free_link *link = memory;

	assert(bytes < MAX_PERMANENT_ALLOC);
	assert(((drccodeptr)memory >= cache->near && (drccodeptr)memory < cache->base) || ((drccodeptr)memory >= cache->end && (drccodeptr)memory < cache->near + cache->size));

	/* determine which free list to add to */
	if ((drccodeptr)memory < cache->base)
		linkptr = &cache->nearfree[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
	else
		linkptr = &cache->free[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];

	/* link is into the free list for our size */
	link->next = *linkptr;
	*linkptr = link;
}


/*-------------------------------------------------
    drccache_memory_alloc_temporary - allocate
    temporary memory from the cache
-------------------------------------------------*/

void *drccache_memory_alloc_temporary(drccache *cache, size_t bytes)
{
	drccodeptr ptr = cache->top;

	/* can't allocate in the middle of codegen */
	assert(cache->codegen == NULL);

	/* if no space, we just fail */
	if (ptr + bytes >= cache->end)
		return NULL;

	/* otherwise, update the cache top */
	cache->top = ALIGN_PTR_UP(ptr + bytes);
	return ptr;
}



/***************************************************************************
    CODE GENERATION
***************************************************************************/

/*-------------------------------------------------
    drccache_begin_codegen - begin code
    generation
-------------------------------------------------*/

drccodeptr *drccache_begin_codegen(drccache *cache, UINT32 reserve_bytes)
{
	drccodeptr ptr = cache->top;

	/* can't restart in the middle of codegen */
	assert(cache->codegen == NULL);
	assert(cache->ooblist == NULL);

	/* if still no space, we just fail */
	if (ptr + reserve_bytes >= cache->end)
		return NULL;

	/* otherwise, return a pointer to the cache top */
	cache->codegen = cache->top;
	cache->oobtail = &cache->ooblist;
	return &cache->top;
}


/*-------------------------------------------------
    drccache_end_codegen - complete code
    generation
-------------------------------------------------*/

drccodeptr drccache_end_codegen(drccache *cache)
{
	drccodeptr result = cache->codegen;

	/* run the OOB handlers */
	while (cache->ooblist != NULL)
	{
		/* remove us from the list */
		oob_handler *oob = cache->ooblist;
		cache->ooblist = oob->next;

		/* call the callback */
		(*oob->callback)(&cache->top, oob->param1, oob->param2, oob->param3);
		assert(cache->top - cache->codegen < CODEGEN_MAX_BYTES);

		/* release our memory */
		drccache_memory_free(cache, oob, sizeof(*oob));
	}

	/* update the cache top */
	cache->top = ALIGN_PTR_UP(cache->top);
	cache->codegen = NULL;

	return result;
}


/*-------------------------------------------------
    drccache_request_oob_codegen - request
    callback for out-of-band codegen
-------------------------------------------------*/

void drccache_request_oob_codegen(drccache *cache, drccache_oob_func callback, void *param1, void *param2, void *param3)
{
	oob_handler *oob;

	assert(cache->codegen != NULL);

	/* pull an item from the free list */
	oob = drccache_memory_alloc(cache, sizeof(*oob));
	assert(oob != NULL);

	/* fill it in */
	oob->next = NULL;
	oob->callback = callback;
	oob->param1 = param1;
	oob->param2 = param2;
	oob->param3 = param3;

	/* add to the tail */
	*cache->oobtail = oob;
	cache->oobtail = &oob->next;
}
