// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************

    drccache.c

    Universal dynamic recompiler cache management.

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

#include "emu.h"
#include "drccache.h"



//**************************************************************************
//  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)))



//**************************************************************************
//  DRC CACHE
//**************************************************************************

//-------------------------------------------------
//  drc_cache - constructor
//-------------------------------------------------

drc_cache::drc_cache(size_t bytes)
	: m_near((drccodeptr)osd_alloc_executable(bytes)),
		m_neartop(m_near),
		m_base(m_near + NEAR_CACHE_SIZE),
		m_top(m_base),
		m_end(m_near + bytes),
		m_codegen(nullptr),
		m_size(bytes)
{
	memset(m_free, 0, sizeof(m_free));
	memset(m_nearfree, 0, sizeof(m_nearfree));
}


//-------------------------------------------------
//  ~drc_cache - destructor
//-------------------------------------------------

drc_cache::~drc_cache()
{
	// release the memory
	osd_free_executable(m_near, m_size);
}



//-------------------------------------------------
//  flush - flush the cache contents
//-------------------------------------------------

void drc_cache::flush()
{
	// can't flush in the middle of codegen
	assert(m_codegen == nullptr);

	// just reset the top back to the base and re-seed
	m_top = m_base;
}


//-------------------------------------------------
//  alloc - allocate permanent memory from the
//  cache
//-------------------------------------------------

void *drc_cache::alloc(size_t bytes)
{
	assert(bytes > 0);

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

	// if no space, we just fail
	drccodeptr ptr = (drccodeptr)ALIGN_PTR_DOWN(m_end - bytes);
	if (m_top > ptr)
		return nullptr;

	// otherwise update the end of the cache
	m_end = ptr;
	return ptr;
}


//-------------------------------------------------
//  alloc_near - allocate permanent memory from
//  the near part of the cache
//-------------------------------------------------

void *drc_cache::alloc_near(size_t bytes)
{
	assert(bytes > 0);

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

	// if no space, we just fail
	drccodeptr ptr = (drccodeptr)ALIGN_PTR_UP(m_neartop);
	if (ptr + bytes > m_base)
		return nullptr;

	// otherwise update the top of the near part of the cache
	m_neartop = ptr + bytes;
	return ptr;
}


//-------------------------------------------------
//  alloc_temporary - allocate temporary memory
//  from the cache
//-------------------------------------------------

void *drc_cache::alloc_temporary(size_t bytes)
{
	// can't allocate in the middle of codegen
	assert(m_codegen == nullptr);

	// if no space, we just fail
	drccodeptr ptr = m_top;
	if (ptr + bytes >= m_end)
		return nullptr;

	// otherwise, update the cache top
	m_top = (drccodeptr)ALIGN_PTR_UP(ptr + bytes);
	return ptr;
}


//-------------------------------------------------
//  free - release permanent memory allocated from
//  the cache
//-------------------------------------------------

void drc_cache::dealloc(void *memory, size_t bytes)
{
	assert(bytes < MAX_PERMANENT_ALLOC);
	assert(((drccodeptr)memory >= m_near && (drccodeptr)memory < m_base) || ((drccodeptr)memory >= m_end && (drccodeptr)memory < m_near + m_size));

	// determine which free list to add to
	free_link **linkptr;
	if ((drccodeptr)memory < m_base)
		linkptr = &m_nearfree[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];
	else
		linkptr = &m_free[(bytes + CACHE_ALIGNMENT - 1) / CACHE_ALIGNMENT];

	// link is into the free list for our size
	free_link *link = (free_link *)memory;
	link->m_next = *linkptr;
	*linkptr = link;
}


//-------------------------------------------------
//  begin_codegen - begin code generation
//-------------------------------------------------

drccodeptr *drc_cache::begin_codegen(UINT32 reserve_bytes)
{
	// can't restart in the middle of codegen
	assert(m_codegen == nullptr);
	assert(m_ooblist.first() == nullptr);

	// if still no space, we just fail
	drccodeptr ptr = m_top;
	if (ptr + reserve_bytes >= m_end)
		return nullptr;

	// otherwise, return a pointer to the cache top
	m_codegen = m_top;
	return &m_top;
}


//-------------------------------------------------
//  end_codegen - complete code generation
//-------------------------------------------------

drccodeptr drc_cache::end_codegen()
{
	drccodeptr result = m_codegen;

	// run the OOB handlers
	oob_handler *oob;
	while ((oob = m_ooblist.detach_head()) != nullptr)
	{
		// call the callback
		oob->m_callback(&m_top, oob->m_param1, oob->m_param2);
		assert(m_top - m_codegen < CODEGEN_MAX_BYTES);

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

	// update the cache top
	m_top = (drccodeptr)ALIGN_PTR_UP(m_top);
	m_codegen = nullptr;

	return result;
}


//-------------------------------------------------
//  request_oob_codegen - request callback for
//  out-of-band codegen
//-------------------------------------------------

void drc_cache::request_oob_codegen(drc_oob_delegate callback, void *param1, void *param2)
{
	assert(m_codegen != nullptr);

	// pull an item from the free list
	oob_handler *oob = (oob_handler *)alloc(sizeof(*oob));
	assert(oob != nullptr);

	// fill it in
	oob->m_callback = callback;
	oob->m_param1 = param1;
	oob->m_param2 = param2;

	// add to the tail
	m_ooblist.append(*oob);
}
