mame/src/emu/cpu/drcbeut.c
2009-03-15 17:12:40 +00:00

681 lines
20 KiB
C

/***************************************************************************
drcbeut.c
Utility functions for dynamic recompiling backends.
Copyright Aaron Giles
Released for general non-commercial use under the MAME license
Visit http://mamedev.org for licensing and usage restrictions.
***************************************************************************/
#include "drcbeut.h"
/***************************************************************************
DEBUGGING
***************************************************************************/
#define LOG_RECOVER (0)
/***************************************************************************
TYPE DEFINITIONS
***************************************************************************/
/* structure holding information about a single label */
typedef struct _drcmap_entry drcmap_entry;
struct _drcmap_entry
{
drcmap_entry * next; /* pointer to next map entry */
drccodeptr codeptr; /* pointer to the relevant code */
UINT32 mapvar; /* map variable id */
UINT32 newval; /* value of the variable starting at codeptr */
};
/* structure describing the state of the code map */
struct _drcmap_state
{
drccache * cache; /* pointer to the cache */
UINT64 uniquevalue; /* unique value used to find the table */
drcmap_entry * head; /* head of the live list */
drcmap_entry ** tailptr; /* pointer to tail of the live list */
UINT32 numvalues; /* number of values in the list */
UINT32 mapvalue[DRCUML_MAPVAR_END - DRCUML_MAPVAR_M0]; /* array of current values */
};
/* structure holding information about a single label */
typedef struct _drclabel drclabel;
struct _drclabel
{
drclabel * next; /* pointer to next label */
drcuml_codelabel label; /* the label specified */
drccodeptr codeptr; /* pointer to the relevant code */
};
/* structure holding a live list of labels */
struct _drclabel_list
{
drccache * cache; /* pointer to the cache */
drclabel * head; /* head of the live list */
};
/***************************************************************************
FUNCTION PROTOTYPES
***************************************************************************/
static void label_list_reset(drclabel_list *list, int fatal_on_leftovers);
static drclabel *label_find_or_allocate(drclabel_list *list, drcuml_codelabel label);
static void label_oob_callback(drccodeptr *codeptr, void *param1, void *param2, void *param3);
/***************************************************************************
HASH TABLE MANAGEMENT
***************************************************************************/
/*-------------------------------------------------
drchash_alloc - allocate memory in the cache
for the hash table tracker (it auto-frees
with the cache)
-------------------------------------------------*/
drchash_state *drchash_alloc(drccache *cache, int modes, int addrbits, int ignorebits)
{
int effaddrbits = addrbits - ignorebits;
drchash_state *drchash;
/* allocate permanent state from the cache */
drchash = (drchash_state *)drccache_memory_alloc(cache, sizeof(*drchash) + modes * sizeof(drchash->base[0]));
if (drchash == NULL)
return NULL;
memset(drchash, 0, sizeof(*drchash) + modes * sizeof(drchash->base[0]));
/* copy in parameters */
drchash->cache = cache;
drchash->modes = modes;
/* compute the sizes of the tables */
drchash->l1bits = effaddrbits / 2;
drchash->l2bits = effaddrbits - drchash->l1bits;
drchash->l1shift = ignorebits + drchash->l2bits;
drchash->l2shift = ignorebits;
drchash->l1mask = (1 << drchash->l1bits) - 1;
drchash->l2mask = (1 << drchash->l2bits) - 1;
/* reset the hash table, which allocates any subsequent tables */
if (!drchash_reset(drchash))
return NULL;
return drchash;
}
/*-------------------------------------------------
drchash_reset - flush existing hash tables and
create new ones
-------------------------------------------------*/
int drchash_reset(drchash_state *drchash)
{
int modenum, entry;
/* allocate an empty l2 hash table */
drchash->emptyl2 = (drccodeptr *)drccache_memory_alloc_temporary(drchash->cache, sizeof(drccodeptr) << drchash->l2bits);
if (drchash->emptyl2 == NULL)
return FALSE;
/* populate it with pointers to the recompile_exit code */
for (entry = 0; entry < (1 << drchash->l2bits); entry++)
drchash->emptyl2[entry] = drchash->nocodeptr;
/* allocate an empty l1 hash table */
drchash->emptyl1 = (drccodeptr **)drccache_memory_alloc_temporary(drchash->cache, sizeof(drccodeptr *) << drchash->l1bits);
if (drchash->emptyl1 == NULL)
return FALSE;
/* populate it with pointers to the empty l2 table */
for (entry = 0; entry < (1 << drchash->l1bits); entry++)
drchash->emptyl1[entry] = drchash->emptyl2;
/* reset the hash tables */
for (modenum = 0; modenum < drchash->modes; modenum++)
drchash->base[modenum] = drchash->emptyl1;
return TRUE;
}
/*-------------------------------------------------
drchash_block_begin - note the beginning of a
block
-------------------------------------------------*/
void drchash_block_begin(drchash_state *drchash, drcuml_block *block, const drcuml_instruction *instlist, UINT32 numinst)
{
int inum;
/* before generating code, pre-allocate any hash entries; we do this by setting dummy hash values */
for (inum = 0; inum < numinst; inum++)
{
const drcuml_instruction *inst = &instlist[inum];
/* if the opcode is a hash, verify that it makes sense and then set a NULL entry */
if (inst->opcode == DRCUML_OP_HASH)
{
assert(inst->numparams == 2);
assert(inst->param[0].type == DRCUML_PTYPE_IMMEDIATE);
assert(inst->param[1].type == DRCUML_PTYPE_IMMEDIATE);
/* if we fail to allocate, we must abort the block */
if (!drchash_set_codeptr(drchash, inst->param[0].value, inst->param[1].value, NULL))
drcuml_block_abort(block);
}
/* if the opcode is a hashjmp to a fixed location, make sure we preallocate the tables */
if (inst->opcode == DRCUML_OP_HASHJMP && inst->param[0].type == DRCUML_PTYPE_IMMEDIATE && inst->param[1].type == DRCUML_PTYPE_IMMEDIATE)
{
/* if we fail to allocate, we must abort the block */
drccodeptr code = drchash_get_codeptr(drchash, inst->param[0].value, inst->param[1].value);
if (!drchash_set_codeptr(drchash, inst->param[0].value, inst->param[1].value, code))
drcuml_block_abort(block);
}
}
}
/*-------------------------------------------------
drchash_block_end - note the end of a block
-------------------------------------------------*/
void drchash_block_end(drchash_state *drchash, drcuml_block *block)
{
/* nothing to do here, yet */
}
/*-------------------------------------------------
drchash_set_default_codeptr - change the
default codeptr
-------------------------------------------------*/
void drchash_set_default_codeptr(drchash_state *drchash, drccodeptr nocodeptr)
{
drccodeptr old = drchash->nocodeptr;
int modenum, l1entry, l2entry;
/* nothing to do if the same */
if (old == nocodeptr)
return;
drchash->nocodeptr = nocodeptr;
/* update the empty L2 table first */
for (l2entry = 0; l2entry < (1 << drchash->l2bits); l2entry++)
drchash->emptyl2[l2entry] = nocodeptr;
/* now scan all existing hashtables for entries */
for (modenum = 0; modenum < drchash->modes; modenum++)
if (drchash->base[modenum] != drchash->emptyl1)
for (l1entry = 0; l1entry < (1 << drchash->l1bits); l1entry++)
if (drchash->base[modenum][l1entry] != drchash->emptyl2)
for (l2entry = 0; l2entry < (1 << drchash->l2bits); l2entry++)
if (drchash->base[modenum][l1entry][l2entry] == old)
drchash->base[modenum][l1entry][l2entry] = nocodeptr;
}
/*-------------------------------------------------
drchash_set_codeptr - set the codeptr for the
given mode/pc
-------------------------------------------------*/
int drchash_set_codeptr(drchash_state *drchash, UINT32 mode, UINT32 pc, drccodeptr code)
{
UINT32 l1 = (pc >> drchash->l1shift) & drchash->l1mask;
UINT32 l2 = (pc >> drchash->l2shift) & drchash->l2mask;
assert(mode < drchash->modes);
/* copy-on-write for the l1 hash table */
if (drchash->base[mode] == drchash->emptyl1)
{
drccodeptr **newtable = (drccodeptr **)drccache_memory_alloc_temporary(drchash->cache, sizeof(drccodeptr *) << drchash->l1bits);
if (newtable == NULL)
return FALSE;
memcpy(newtable, drchash->emptyl1, sizeof(drccodeptr *) << drchash->l1bits);
drchash->base[mode] = newtable;
}
/* copy-on-write for the l2 hash table */
if (drchash->base[mode][l1] == drchash->emptyl2)
{
drccodeptr *newtable = (drccodeptr *)drccache_memory_alloc_temporary(drchash->cache, sizeof(drccodeptr) << drchash->l2bits);
if (newtable == NULL)
return FALSE;
memcpy(newtable, drchash->emptyl2, sizeof(drccodeptr) << drchash->l2bits);
drchash->base[mode][l1] = newtable;
}
/* set the new entry */
drchash->base[mode][l1][l2] = code;
return TRUE;
}
/***************************************************************************
CODE MAP MANAGEMENT
***************************************************************************/
/*-------------------------------------------------
drcmap_alloc - allocate memory in the cache
for the code mapper (it auto-frees with the
cache)
-------------------------------------------------*/
drcmap_state *drcmap_alloc(drccache *cache, UINT64 uniquevalue)
{
drcmap_state *drcmap;
/* allocate permanent state from the cache */
drcmap = (drcmap_state *)drccache_memory_alloc(cache, sizeof(*drcmap));
if (drcmap == NULL)
return NULL;
memset(drcmap, 0, sizeof(*drcmap));
/* remember the cache */
drcmap->cache = cache;
drcmap->tailptr = &drcmap->head;
return drcmap;
}
/*-------------------------------------------------
drcmap_block_begin - note the beginning of a
block
-------------------------------------------------*/
void drcmap_block_begin(drcmap_state *drcmap, drcuml_block *block)
{
/* release any remaining live entries */
while (drcmap->head != NULL)
{
drcmap_entry *entry = drcmap->head;
drcmap->head = entry->next;
drccache_memory_free(drcmap->cache, entry, sizeof(*entry));
}
/* reset the tailptr and count */
drcmap->tailptr = &drcmap->head;
drcmap->numvalues = 0;
/* reset the variable values */
memset(drcmap->mapvalue, 0, sizeof(drcmap->mapvalue));
}
/*-------------------------------------------------
drcmap_block_end - note the end of a block
-------------------------------------------------*/
void drcmap_block_end(drcmap_state *drcmap, drcuml_block *block)
{
UINT32 curvalue[DRCUML_MAPVAR_END - DRCUML_MAPVAR_M0] = { 0 };
UINT8 changed[DRCUML_MAPVAR_END - DRCUML_MAPVAR_M0] = { 0 };
drcmap_entry *entry;
drccodeptr lastptr;
drccodeptr *top;
UINT32 *dest;
/* only process if we have data */
if (drcmap->head == NULL)
return;
/* begin "code generation" aligned to an 8-byte boundary */
top = drccache_begin_codegen(drcmap->cache, sizeof(UINT64) + sizeof(UINT32) + 2 * sizeof(UINT32) * drcmap->numvalues);
if (top == NULL)
drcuml_block_abort(block);
dest = (UINT32 *)(((FPTR)*top + 7) & ~7);
/* store the cookie first */
*(UINT64 *)dest = drcmap->uniquevalue;
dest += 2;
/* get the pointer to the first item and store an initial backwards offset */
lastptr = drcmap->head->codeptr;
*dest = (drccodeptr)dest - lastptr;
dest++;
/* now iterate over entries and store them */
for (entry = drcmap->head; entry != NULL; entry = entry->next)
{
/* update the current value of the variable and detect changes */
if (curvalue[entry->mapvar] != entry->newval)
{
curvalue[entry->mapvar] = entry->newval;
changed[entry->mapvar] = TRUE;
}
/* if the next code pointer is different, or if we're at the end, flush changes */
if (entry->next == NULL || entry->next->codeptr != entry->codeptr)
{
UINT32 codedelta = entry->codeptr - lastptr;
UINT32 varmask = 0;
int numchanged;
int varnum;
/* build a mask of changed variables */
for (numchanged = varnum = 0; varnum < ARRAY_LENGTH(changed); varnum++)
if (changed[varnum])
{
changed[varnum] = FALSE;
varmask |= 1 << varnum;
numchanged++;
}
/* if nothing really changed, skip it */
if (numchanged == 0)
continue;
/* first word is a code delta plus mask of changed variables */
while (codedelta > 0xffff)
{
*dest++ = 0xffff << 16;
codedelta -= 0xffff;
}
*dest++ = (codedelta << 16) | (varmask << 4) | numchanged;
/* now output updated variable values */
for (varnum = 0; varnum < ARRAY_LENGTH(changed); varnum++)
if ((varmask >> varnum) & 1)
*dest++ = curvalue[varnum];
/* remember our lastptr */
lastptr = entry->codeptr;
}
}
/* add a terminator */
*dest++ = 0;
/* complete codegen */
*top = (drccodeptr)dest;
drccache_end_codegen(drcmap->cache);
}
/*-------------------------------------------------
drcmap_set_value - set a map value for the
given code pointer
-------------------------------------------------*/
void drcmap_set_value(drcmap_state *drcmap, drccodeptr codebase, UINT32 mapvar, UINT32 newvalue)
{
drcmap_entry *entry;
assert(mapvar >= DRCUML_MAPVAR_M0 && mapvar < DRCUML_MAPVAR_END);
/* if this value isn't different, skip it */
if (drcmap->mapvalue[mapvar - DRCUML_MAPVAR_M0] == newvalue)
return;
/* allocate a new entry and fill it in */
entry = (drcmap_entry *)drccache_memory_alloc(drcmap->cache, sizeof(*entry));
entry->next = NULL;
entry->codeptr = codebase;
entry->mapvar = mapvar - DRCUML_MAPVAR_M0;
entry->newval = newvalue;
/* hook us into the end of the list */
*drcmap->tailptr = entry;
drcmap->tailptr = &entry->next;
/* update our state in the table as well */
drcmap->mapvalue[mapvar - DRCUML_MAPVAR_M0] = newvalue;
/* and increment the count */
drcmap->numvalues++;
}
/*-------------------------------------------------
drcmap_get_value - return a map value for the
given code pointer
-------------------------------------------------*/
UINT32 drcmap_get_value(drcmap_state *drcmap, drccodeptr codebase, UINT32 mapvar)
{
UINT64 *endscan = (UINT64 *)drccache_top(drcmap->cache);
UINT32 varmask = 0x10 << mapvar;
drccodeptr curcode;
UINT32 result = 0;
UINT64 *curscan;
UINT32 *data;
assert(mapvar >= DRCUML_MAPVAR_M0 && mapvar < DRCUML_MAPVAR_END);
mapvar -= DRCUML_MAPVAR_M0;
/* get an aligned pointer to start scanning */
curscan = (UINT64 *)(((FPTR)codebase | 7) + 1);
/* look for the signature */
while (curscan < endscan && *curscan++ != drcmap->uniquevalue) ;
if (curscan >= endscan)
return 0;
/* switch to 32-bit pointers for processing the rest */
data = (UINT32 *)curscan;
/* first get the 32-bit starting offset to the code */
curcode = (drccodeptr)data - *data;
data++;
/* now loop until we advance past our target */
while (TRUE)
{
UINT32 controlword = *data++;
/* a 0 is a terminator */
if (controlword == 0)
break;
/* update the codeptr; if this puts us past the end, we're done */
curcode += (controlword >> 16) & 0xffff;
if (curcode > codebase)
break;
/* if our mapvar has changed, process this word */
if ((controlword & varmask) != 0)
{
int dataoffs = 0;
UINT32 skipmask;
/* count how many words precede the one we care about */
for (skipmask = (controlword & (varmask - 1)) >> 4; skipmask != 0; skipmask = skipmask & (skipmask - 1))
dataoffs++;
/* fetch the one we want */
result = data[dataoffs];
}
/* low 4 bits contain the total number of words of data */
data += controlword & 0x0f;
}
if (LOG_RECOVER)
printf("recover %d @ %p = %08X\n", mapvar, codebase, result);
return result;
}
/*-------------------------------------------------
drcmap_get_last_value - return the most
recently set map value
-------------------------------------------------*/
UINT32 drcmap_get_last_value(drcmap_state *drcmap, UINT32 mapvar)
{
assert(mapvar >= DRCUML_MAPVAR_M0 && mapvar < DRCUML_MAPVAR_END);
return drcmap->mapvalue[mapvar - DRCUML_MAPVAR_M0];
}
/***************************************************************************
LABEL MANAGEMENT
***************************************************************************/
/*-------------------------------------------------
drclabel_list_alloc - allocate a label
list within the cache (it auto-frees with the
cache)
-------------------------------------------------*/
drclabel_list *drclabel_list_alloc(drccache *cache)
{
drclabel_list *list;
/* allocate permanent state from the cache */
list = (drclabel_list *)drccache_memory_alloc(cache, sizeof(*list));
if (list == NULL)
return NULL;
memset(list, 0, sizeof(*list));
/* remember the cache */
list->cache = cache;
return list;
}
/*-------------------------------------------------
drclabel_block_begin - note the beginning of
a block
-------------------------------------------------*/
void drclabel_block_begin(drclabel_list *list, drcuml_block *block)
{
/* make sure the label list is clear, but don't fatalerror */
label_list_reset(list, FALSE);
}
/*-------------------------------------------------
drclabel_block_end - note the end of a block
-------------------------------------------------*/
void drclabel_block_end(drclabel_list *list, drcuml_block *block)
{
/* make sure the label list is clear, and fatalerror if we missed anything */
label_list_reset(list, TRUE);
}
/*-------------------------------------------------
drclabel_get_codeptr - find or allocate a new
label; returns NULL and requests an OOB
callback if undefined
-------------------------------------------------*/
drccodeptr drclabel_get_codeptr(drclabel_list *list, drcuml_codelabel label, drclabel_fixup_func fixup, void *param)
{
drclabel *curlabel = label_find_or_allocate(list, label);
/* if no code pointer, request an OOB callback */
if (curlabel->codeptr == NULL && fixup != NULL)
drccache_request_oob_codegen(list->cache, label_oob_callback, curlabel, (void *)fixup, param);
return curlabel->codeptr;
}
/*-------------------------------------------------
drclabel_set_codeptr - set the pointer to a new
label
-------------------------------------------------*/
void drclabel_set_codeptr(drclabel_list *list, drcuml_codelabel label, drccodeptr codeptr)
{
/* set the code pointer */
drclabel *curlabel = label_find_or_allocate(list, label);
assert(curlabel->codeptr == NULL);
curlabel->codeptr = codeptr;
}
/***************************************************************************
LABEL MANAGEMENT
***************************************************************************/
/*-------------------------------------------------
label_list_reset - reset a label
list (add all entries to the free list)
-------------------------------------------------*/
static void label_list_reset(drclabel_list *list, int fatal_on_leftovers)
{
/* loop until out of labels */
while (list->head != NULL)
{
/* remove from the list */
drclabel *label = list->head;
list->head = label->next;
/* fatal if we were a leftover */
if (fatal_on_leftovers && label->codeptr == NULL)
fatalerror("Label %08X never defined!", label->label);
/* free the label */
drccache_memory_free(list->cache, label, sizeof(*label));
}
}
/*-------------------------------------------------
label_find_or_allocate - look up a label and
allocate a new one if not found
-------------------------------------------------*/
static drclabel *label_find_or_allocate(drclabel_list *list, drcuml_codelabel label)
{
drclabel *curlabel;
/* find the label, or else allocate a new one */
for (curlabel = list->head; curlabel != NULL; curlabel = curlabel->next)
if (curlabel->label == label)
break;
/* if none found, allocate */
if (curlabel == NULL)
{
curlabel = (drclabel *)drccache_memory_alloc(list->cache, sizeof(*curlabel));
curlabel->next = list->head;
curlabel->label = label;
curlabel->codeptr = NULL;
list->head = curlabel;
}
return curlabel;
}
/*-------------------------------------------------
label_oob_callback - out-of-band codegen
callback for labels
-------------------------------------------------*/
static void label_oob_callback(drccodeptr *codeptr, void *param1, void *param2, void *param3)
{
drclabel *label = (drclabel *)param1;
drclabel_fixup_func callback = (drclabel_fixup_func)param2;
(*callback)(param3, label->codeptr);
}