syn68k/runtime/native.c

685 lines
19 KiB
C

#include "syn68k_private.h"
#ifdef GENERATE_NATIVE_CODE
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include "native.h"
#ifdef DEBUG
#include <stdio.h>
#endif
static void host_uncache_reg (cache_info_t *c, int guest_reg);
/* This is just a default template to copy whenever you need to set up
* an empty cache. Never modify it (except when it's initially set up,
* of course).
*/
cache_info_t empty_cache_info;
void
native_code_init ()
{
int i;
/* Set up an empty cache info struct that we can just copy whenever
* we need to set up an empty cache.
*/
memset (&empty_cache_info, 0, sizeof empty_cache_info);
for (i = 0; i < NUM_GUEST_REGS; i++)
{
guest_reg_status_t *r = &empty_cache_info.guest_reg_status[i];
r->host_reg = NO_REG;
r->mapping = MAP_NATIVE;
r->dirty_without_offset_p = FALSE;
r->offset = 0;
}
for (i = 0; i < NUM_HOST_REGS; i++)
empty_cache_info.host_reg_to_guest_reg[i] = NO_REG;
empty_cache_info.cached_cc = M68K_CC_NONE;
empty_cache_info.dirty_cc = M68K_CC_NONE;
host_native_code_init ();
}
#ifdef DEBUG
static void
verify_cache_consistency (const cache_info_t *c)
{
int h, g;
for (h = 0; h < NUM_HOST_REGS; h++)
{
g = c->host_reg_to_guest_reg[h];
if (g != NO_REG)
if (c->guest_reg_status[g].host_reg != h)
{
fprintf (stderr, "Internal error! register/cc cache internally "
"inconsistent. Host reg %d claims to be in guest reg "
"%d, but guest reg %d claims to be cached in host reg "
"%d. Wedging.\n",
h, g, g, c->guest_reg_status[g].host_reg);
while (1)
;
}
}
for (g = 0; g < NUM_GUEST_REGS; g++)
{
h = c->guest_reg_status[g].host_reg;
if (h != NO_REG)
if (c->host_reg_to_guest_reg[h] != g)
{
fprintf (stderr, "Internal error! register/cc cache internally "
"inconsistent. Guest reg %d claims to be cached in "
"host reg %d, but host reg %d claims to be cacheing "
"guest reg %d. Wedging.\n",
g, h, h, c->host_reg_to_guest_reg[h]);
while (1)
;
}
}
#ifdef i386
/* Make sure data registers only go into byte-addressable host regs. */
for (g = 0; g < 8; g++)
{
h = c->guest_reg_status[g].host_reg;
if (h != NO_REG && !((1L << h) & REGSET_BYTE))
{
fprintf (stderr, "Internal error! Put data register d%d into "
"a non-byte accessible host register (%d). Wedging.\n",
g, h);
while (1)
;
}
}
#endif /* i386 */
}
#endif /* DEBUG */
int
host_setup_cached_reg (COMMON_ARGS, int guest_reg,
unsigned acceptable_mappings,
host_reg_mask_t acceptable_regs)
{
guest_reg_status_t *r;
int host_reg;
r = &c->guest_reg_status[guest_reg];
host_reg = r->host_reg;
if (host_reg == NO_REG)
{
host_reg = host_alloc_reg (c, codep, cc_spill_if_changed,
acceptable_regs);
if (host_reg == NO_REG)
return NO_REG;
host_cache_reg (c, codep, cc_spill_if_changed, guest_reg, host_reg);
}
else if (!((1L << host_reg) & acceptable_regs))
{
int new_reg;
/* If they are demanding a register other than the one it's in,
* move it to an acceptable one.
*/
new_reg = host_alloc_reg (c, codep, cc_spill_if_changed,
acceptable_regs);
if (new_reg == NO_REG)
return NO_REG;
if (host_movel_reg_reg (COMMON_ARG_NAMES, host_reg, new_reg))
return NO_REG;
r->host_reg = new_reg;
c->host_reg_to_guest_reg[host_reg] = NO_REG;
c->host_reg_to_guest_reg[new_reg] = guest_reg;
host_reg = new_reg;
}
/* If the register's mapping is already acceptable, then we're done. */
if ((1L << r->mapping) & acceptable_mappings)
return host_reg;
switch (r->mapping)
{
case MAP_NATIVE:
if (acceptable_mappings & MAP_OFFSET_MASK)
{
r->offset = 0;
r->mapping = MAP_OFFSET;
}
#ifdef LITTLEENDIAN
else if (acceptable_mappings & MAP_SWAP16_MASK)
{
host_swap16 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_SWAP16;
}
else if (acceptable_mappings & MAP_SWAP32_MASK)
{
host_swap32 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_SWAP32;
}
#endif /* LITTLEENDIAN */
else
return NO_REG;
break;
case MAP_OFFSET:
if (acceptable_mappings
& (MAP_NATIVE_MASK
#ifdef LITTLEENDIAN
| MAP_SWAP16_MASK | MAP_SWAP32_MASK
))
#endif /* LITTLEENDIAN */
{
host_unoffset_reg (c, codep, cc_spill_if_changed,
guest_reg);
if (acceptable_mappings & MAP_NATIVE_MASK)
r->mapping = MAP_NATIVE;
#ifdef LITTLEENDIAN
else if (acceptable_mappings & MAP_SWAP16_MASK)
{
host_swap16 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_SWAP16;
}
else /* if (acceptable_mappings & MAP_SWAP32_MASK) */
{
host_swap32 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_SWAP32;
}
#endif /* LITTLEENDIAN */
}
else
return NO_REG;
break;
#ifdef LITTLEENDIAN
case MAP_SWAP16:
if (acceptable_mappings & MAP_NATIVE_MASK)
{
host_swap16 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_NATIVE;
}
else if (acceptable_mappings & MAP_OFFSET_MASK)
{
host_swap16 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->offset = 0;
r->mapping = MAP_OFFSET;
}
else if (acceptable_mappings & MAP_SWAP32_MASK)
{
host_swap16_to_32 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_SWAP32;
}
else
return NO_REG;
break;
case MAP_SWAP32:
if (acceptable_mappings & MAP_NATIVE_MASK)
{
host_swap32 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_NATIVE;
}
else if (acceptable_mappings & MAP_OFFSET_MASK)
{
host_swap32 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->offset = 0;
r->mapping = MAP_OFFSET;
}
else if (acceptable_mappings & MAP_SWAP16_MASK)
{
host_swap32_to_16 (c, codep, cc_spill_if_changed,
M68K_CC_NONE, NO_REG, host_reg);
r->mapping = MAP_SWAP16;
}
else
return NO_REG;
break;
#endif /* LITTLEENDIAN */
default:
abort ();
}
return host_reg;
}
#ifdef GCD_RANGE
#warning "Temp debugging stuff!"
const guest_code_descriptor_t *lowest_gcd = 0;
const guest_code_descriptor_t *highest_gcd = (guest_code_descriptor_t *)~0;
#endif
/* Generates native code for the specified list of guest_code_descriptor_t's
* at the address pointed to by *code if possible. If successful, returns
* TRUE and updates *code to point just past the end of the newly generated
* code. Otherwise, returns FALSE and *code is unaffected. cc_to_compute
* should always be a subset of cc_live.
*/
BOOL
generate_native_code (const guest_code_descriptor_t *first_desc,
cache_info_t *cache_info,
const int32 *orig_operands,
host_code_t **code,
uint8 cc_input, uint8 cc_live, uint8 cc_to_compute,
BOOL ends_block_p,
Block *block,
const uint16 *m68k_code)
{
const guest_code_descriptor_t *desc;
const reg_operand_info_t *op;
host_code_t *orig_code;
cache_info_t orig_cache_info;
guest_reg_status_t *r;
int i, guest_reg, host_reg, scratch_reg;
unsigned cc_dont_touch, cc_dont_touch_during_setup;
backpatch_t *orig_backpatch, *bp, *bp_next, *old_first_backpatch;
#ifdef GCD_RANGE
#warning "Temp debugging stuff!"
if (first_desc < lowest_gcd || first_desc > highest_gcd)
return 0;
#endif
/* Save these away in case we realize a mistake and we have to start over. */
orig_code = *code;
orig_cache_info = *cache_info;
orig_backpatch = block->backpatch;
block->backpatch = NULL;
#ifdef DEBUG
verify_cache_consistency (cache_info);
#endif
/* Compute those cc bits we are not allowed to modify (without spilling). */
cc_dont_touch = cc_live & cache_info->cached_cc & ~cc_to_compute;
cc_dont_touch_during_setup = ((cc_dont_touch | cc_input)
& cache_info->cached_cc);
/* See if we find any match in the list of code descriptors, and fill
* in the operands array.
*/
for (desc = first_desc; desc != NULL; desc = desc->next)
{
int32 operands[MAX_BITFIELDS], canonical_operands[MAX_BITFIELDS];
host_reg_mask_t locked_host_reg;
uint8 acceptable_mapping[NUM_GUEST_REGS];
/* If insufficient cc bits are cached, bail out quickly. */
if ((cache_info->cached_cc & desc->cc_in) != desc->cc_in)
goto failure_no_copy;
/* If this guy doesn't compute enough cc bits, bail out quickly. */
if ((cc_to_compute & desc->cc_out) != cc_to_compute)
goto failure_no_copy;
/* Create the "canonical" set of operands to use. Right now this
* means just adding 8 to address register operands.
*/
memcpy (canonical_operands, orig_operands, sizeof canonical_operands);
for (op = desc->reg_op_info; op->legitimate_p; op++)
{
if (op->add8_p)
{
int opnum = op->operand_num;
canonical_operands[opnum] = (orig_operands[opnum] & 7) + 8;
}
}
/* Figure out exactly which mappings are acceptable for every
* guest register mentioned. If one of them has no acceptable
* mapping, bail out.
*/
memset (acceptable_mapping, MAP_ALL_MASK, sizeof acceptable_mapping);
for (op = desc->reg_op_info; op->legitimate_p; op++)
{
int guest_reg = canonical_operands[op->operand_num];
assert (guest_reg >= 0 && guest_reg < NUM_GUEST_REGS);
acceptable_mapping[guest_reg] &= op->acceptable_mapping;
if (acceptable_mapping[guest_reg] == 0)
goto failure;
}
/* Default to new operands being the same as the old operands. */
memcpy (operands, canonical_operands, sizeof operands);
locked_host_reg = (host_reg_mask_t)~ALLOCATABLE_REG_MASK;
#if 0
/* I think this is causing extra unoffsetting. */
/* If this instruction actually computes live cc bits, un-offset
* registers NOW. Otherwise, we might be forced to un-offset the
* registers between a compare and a conditional branch (for example),
* and the process of offsetting is less efficient if we can't
* step on cc bits.
*/
if (cc_live & cc_to_compute & desc->cc_out)
host_unoffset_regs (cache_info, code, cc_dont_touch);
#endif
/* Cache registers as necessary and with the appropriate byte order. */
for (op = desc->reg_op_info; op->legitimate_p; op++)
{
unsigned acceptable;
guest_reg = canonical_operands[op->operand_num];
assert (guest_reg >= 0 && guest_reg < NUM_GUEST_REGS);
r = &cache_info->guest_reg_status[guest_reg];
host_reg = r->host_reg;
switch (op->request_type)
{
case REQUEST_REG:
acceptable = acceptable_mapping[guest_reg];
/* Allocate a register, if necessary. */
if (host_reg == NO_REG)
{
host_reg = host_alloc_reg (cache_info, code,
cc_dont_touch_during_setup,
op->regset & ~locked_host_reg);
if (host_reg == NO_REG)
goto failure;
operands[op->operand_num] = host_reg;
host_cache_reg (cache_info, code, cc_dont_touch_during_setup,
guest_reg, host_reg);
}
/* Map to an acceptable byte order or offset and
* particular register.
*/
if (!((1L << r->mapping) & acceptable)
|| !(op->regset & (1L << host_reg)))
{
host_reg = host_setup_cached_reg (cache_info, code,
cc_dont_touch_during_setup,
M68K_CC_NONE, NO_REG,
guest_reg, acceptable,
(op->regset
& ~locked_host_reg));
if (host_reg == NO_REG)
goto failure;
}
operands[op->operand_num] = host_reg;
locked_host_reg |= (1L << host_reg);
break;
case REQUEST_SPARE_REG:
/* Allocate a register, if necessary. */
if (host_reg == NO_REG)
{
host_reg = host_alloc_reg (cache_info, code,
cc_dont_touch_during_setup,
op->regset & ~locked_host_reg);
/* If we failed to find one, bail out! */
if (host_reg == NO_REG)
goto failure;
r->host_reg = host_reg;
r->mapping = MAP_NATIVE; /* default */
r->dirty_without_offset_p = FALSE;
cache_info->host_reg_to_guest_reg[host_reg] = guest_reg;
}
locked_host_reg |= (1L << host_reg);
operands[op->operand_num] = host_reg;
break;
default:
abort ();
}
}
/* If insufficient cc bits are cached at this point, we failed. */
if ((cache_info->cached_cc & desc->cc_in) != desc->cc_in)
goto failure;
#ifdef LITTLEENDIAN
/* If they are computing live cc bits, and we have swapped dirty
* registers lying around, let's swap them back now. That way we
* won't be forced to swap right before a conditional branch and
* throw away our hard-earned cc bits. This is a heuristic; it
* is not necessary for correct behavior.
*/
if (cc_live & cc_to_compute & desc->cc_out)
{
int hr;
for (hr = NUM_HOST_REGS - 1; hr >= 0; hr--)
if (!(locked_host_reg & (1L << hr)))
{
int gr = cache_info->host_reg_to_guest_reg[hr];
if (gr != NO_REG)
{
guest_reg_status_t *grs;
grs = &cache_info->guest_reg_status[gr];
if (grs->dirty_without_offset_p)
{
if (grs->mapping == MAP_SWAP16)
{
host_swap16 (cache_info, code,
cc_dont_touch_during_setup,
M68K_CC_NONE, NO_REG, hr);
grs->mapping = MAP_NATIVE;
}
else if (grs->mapping == MAP_SWAP32
#ifdef i386
&& !have_i486_p /* bswap doesn't touch ccs */
#endif
)
{
host_swap32 (cache_info, code,
cc_dont_touch_during_setup,
M68K_CC_NONE, NO_REG, hr);
grs->mapping = MAP_NATIVE;
}
}
}
}
}
#endif /* LITTLEENDIAN */
/* Allocate a scratch register if one was requested. */
if (desc->scratch_reg != 0)
{
scratch_reg = host_alloc_reg (cache_info, code,
cc_dont_touch_during_setup,
desc->scratch_reg & ~locked_host_reg);
if (scratch_reg == NO_REG)
goto failure;
locked_host_reg |= 1L << scratch_reg;
}
else
scratch_reg = NO_REG;
/* Crank out the native code. Note that we may pass in more
* arguments than the function is expecting because our function
* pointer can point to handlers that care about different numbers
* of operands; technically that's invalid C, but it will work on
* any reasonable machine. If any of the compiler functions
* return nonzero, we abort this compile and try something else.
*/
old_first_backpatch = NULL;
for (i = 0; i < MAX_COMPILE_FUNCS; i++)
{
const oporder_t *order;
void *ops[4];
int j, (*f)();
host_code_t *code_start;
int32 offset;
f = desc->compile_func[i].func;
if (f == NULL)
break;
/* Scramble the operands appropriately. */
order = &desc->compile_func[i].order;
for (j = 0; j < MAX_M68K_OPERANDS; j++)
{
int n = order->operand_num[j];
if (n >= 0)
ops[j] = (void *)operands[n];
else
{
switch (n)
{
case USE_SCRATCH_REG:
assert (scratch_reg != NO_REG);
ops[j] = (void *)scratch_reg;
break;
case USE_BLOCK:
ops[j] = (void *)block;
break;
case USE_MINUS_ONE:
ops[j] = (void *)-1;
break;
case USE_ZERO:
ops[j] = (void *)0;
break;
case USE_0xFFFF:
ops[j] = (void *)0xFFFF;
break;
case USE_M68K_PC:
ops[j] = (void *)m68k_code;
break;
case USE_M68K_PC_PLUS_TWO:
ops[j] = (void *)m68k_code + 2;
break;
case USE_M68K_PC_PLUS_FOUR:
ops[j] = (void *)m68k_code + 4;
break;
default:
abort ();
}
}
}
code_start = *code;
if ((*f)(cache_info, code, cc_dont_touch, cc_to_compute,
scratch_reg, ops[0], ops[1], ops[2], ops[3]))
goto failure;
/* Check for new backpatches. If we found any, adjust their
* starting addresses to be relative to the start of all native
* code generated here, rather than just the native code generated
* by the last function call.
*/
offset = ((char *)code_start - (char *)orig_code) * 8;
for (bp = block->backpatch; bp != old_first_backpatch; bp = bp->next)
bp->offset_location += offset;
old_first_backpatch = block->backpatch;
}
/* Update the cache_info to reflect the new cached register status. */
if (!ends_block_p) /* Don't bother if the cache is dead anyway. */
{
/* Update the cached register information. */
for (op = desc->reg_op_info; op->legitimate_p; op++)
{
guest_reg = canonical_operands[op->operand_num];
assert (guest_reg >= 0 && guest_reg < NUM_GUEST_REGS);
r = &cache_info->guest_reg_status[guest_reg];
host_reg = r->host_reg;
switch (op->output_state)
{
case ROS_UNTOUCHED:
break;
case ROS_UNTOUCHED_DIRTY:
r->dirty_without_offset_p = TRUE;
break;
case ROS_INVALID:
host_uncache_reg (cache_info, guest_reg);
break;
case ROS_NATIVE:
case ROS_OFFSET:
case ROS_NATIVE_DIRTY:
case ROS_OFFSET_DIRTY:
#ifdef LITTLEENDIAN
case ROS_SWAP16:
case ROS_SWAP32:
case ROS_SWAP16_DIRTY:
case ROS_SWAP32_DIRTY:
#endif /* LITTLEENDIAN */
r->mapping = (op->output_state & 3);
r->dirty_without_offset_p = !!(op->output_state
& ROS_DIRTY_BIT);
break;
default:
abort ();
}
}
}
#ifdef DEBUG
verify_cache_consistency (cache_info);
#endif
/* Prepend all new backpatches to the final list. */
for (bp = block->backpatch; bp != NULL; bp = bp_next)
{
bp_next = bp->next;
bp->next = orig_backpatch;
orig_backpatch = bp;
}
block->backpatch = orig_backpatch;
/* Success! */
return TRUE;
failure:
/* No luck with this description; reset everything and try the next.
* Note that we have to restore everything even if we have no
* descriptions left to try, since our caller needs to know what
* they should spill if they transition to synthetic code.
*/
*code = orig_code;
*cache_info = orig_cache_info;
failure_no_copy:
for (bp = block->backpatch; bp != NULL; bp = bp_next)
{
bp_next = bp->next;
free (bp);
}
block->backpatch = NULL;
}
#ifdef DEBUG
verify_cache_consistency (cache_info);
#endif
/* We failed to generate the desired native code. */
block->backpatch = orig_backpatch;
return FALSE;
}
static void
host_uncache_reg (cache_info_t *c, int guest_reg)
{
guest_reg_status_t *r;
int host_reg;
r = &c->guest_reg_status[guest_reg];
host_reg = r->host_reg;
if (host_reg != NO_REG)
{
c->host_reg_to_guest_reg[host_reg] = NO_REG;
r->host_reg = NO_REG;
}
}
#endif /* GENERATE_NATIVE_CODE */