mirror of
https://github.com/ctm/syn68k.git
synced 2024-11-25 07:32:17 +00:00
1381 lines
40 KiB
C
1381 lines
40 KiB
C
#include "xlate.h"
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
/* This file builds some of the lookup tables used at runtime for
|
|
* native code generation. It takes the contents of xlatetable.c
|
|
* as input.
|
|
*/
|
|
|
|
static guest_code_descriptor_t *alloc_gcd (void);
|
|
static char *create_name (const char *n, int size, int src_amode,
|
|
int dst_amode, int which);
|
|
static guest_code_descriptor_t *process_move (const xlate_descriptor_t *x);
|
|
static guest_code_descriptor_t *process_binary (const xlate_descriptor_t *x);
|
|
static guest_code_descriptor_t *process_unary (const xlate_descriptor_t *x);
|
|
static void default_desc (const xlate_descriptor_t *x, size_mask_t size,
|
|
guest_code_descriptor_t *g);
|
|
static int compare_regops (const void *p1, const void *p2);
|
|
|
|
|
|
guest_code_descriptor_t *
|
|
process_xlate_descriptor (const xlate_descriptor_t *x)
|
|
{
|
|
guest_code_descriptor_t *g, *gd;
|
|
|
|
switch (x->type)
|
|
{
|
|
case OP_UNARY:
|
|
g = process_unary (x);
|
|
break;
|
|
case OP_BINARY:
|
|
g = process_binary (x);
|
|
break;
|
|
case OP_MOVE:
|
|
g = process_move (x);
|
|
break;
|
|
default:
|
|
g = NULL;
|
|
abort ();
|
|
}
|
|
|
|
/* Sort the regop arrays for correctness and efficiency. */
|
|
for (gd = g; gd != NULL; gd = gd->next)
|
|
{
|
|
int i;
|
|
|
|
/* Count the number of legitmate reg operands. */
|
|
for (i = 0; gd->reg_op_info[i].legitimate_p; i++)
|
|
;
|
|
qsort (&gd->reg_op_info, i, sizeof gd->reg_op_info[0], compare_regops);
|
|
}
|
|
|
|
return g;
|
|
}
|
|
|
|
|
|
static int
|
|
bits (unsigned n)
|
|
{
|
|
int b;
|
|
for (b = 0; n != 0; n &= n - 1)
|
|
b++;
|
|
return b;
|
|
}
|
|
|
|
|
|
static int
|
|
compare_regops (const void *p1, const void *p2)
|
|
{
|
|
const reg_operand_info_t *r1, *r2;
|
|
|
|
r1 = (const reg_operand_info_t *)p1;
|
|
r2 = (const reg_operand_info_t *)p2;
|
|
|
|
/* We _must_ put all REQUEST_SPARE_REG operands last! */
|
|
if (r1->request_type != r2->request_type)
|
|
return r1->request_type - r2->request_type;
|
|
|
|
/* Put all address registers first, to tend to avoid AGI delays
|
|
* on the x86 (this is a free optimization, so why not?)
|
|
*/
|
|
if (r1->add8_p != r2->add8_p)
|
|
return r2->add8_p - r1->add8_p;
|
|
|
|
/* Put the most restrictive allowable host regsets first. */
|
|
if (bits (r1->regset) != bits (r2->regset))
|
|
return bits (r1->regset) - bits (r2->regset);
|
|
|
|
/* Put the most restrictive mappings first. */
|
|
if (bits (r1->acceptable_mapping) != bits (r2->acceptable_mapping))
|
|
return bits (r1->acceptable_mapping) - bits (r2->acceptable_mapping);
|
|
|
|
/* Default to sorting by operand number, highest first. Higher operands
|
|
* tend to be source operands, so we might as well put them first to
|
|
* avoid AGI delays.
|
|
*/
|
|
return r2->operand_num - r1->operand_num;
|
|
}
|
|
|
|
|
|
/* Returns TRUE if the operation is a bitwise one, in which case we
|
|
* don't need to byteswap quite so often (or we can byteswap the
|
|
* constant). We can't do this sort of tomfoolery when we need
|
|
* certain cc bits, though.
|
|
*/
|
|
static int
|
|
bitwise_op_p (const char *i386_op)
|
|
{
|
|
static const char *known_bitwise_ops[] = { "and", "or", "xor", "not" };
|
|
int i;
|
|
|
|
for (i = 0; i < NELEM (known_bitwise_ops); i++)
|
|
if (!strcmp (i386_op, known_bitwise_ops[i]))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static int
|
|
compare_op_p (const char *i386_op)
|
|
{
|
|
static const char *known_compare_ops[] = { "cmp", "test" };
|
|
int i;
|
|
|
|
for (i = 0; i < NELEM (known_compare_ops); i++)
|
|
if (!strcmp (i386_op, known_compare_ops[i]))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static int
|
|
shift_op_p (const char *i386_op)
|
|
{
|
|
static const char *known_shift_ops[] = { "sal", "sar", "shl", "shr" };
|
|
int i;
|
|
|
|
for (i = 0; i < NELEM (known_shift_ops); i++)
|
|
if (!strcmp (i386_op, known_shift_ops[i]))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#ifdef __GNUC__
|
|
#define G(x) x
|
|
#else
|
|
#define G(x)
|
|
#endif
|
|
|
|
|
|
/* This table maps an addressing mode to how many registers it uses. */
|
|
static const int amode_reg_operands[] =
|
|
{
|
|
G ([AMODE_NONE]=) 0,
|
|
G ([AMODE_IMM]=) 0,
|
|
G ([AMODE_REG]=) 1,
|
|
G ([AMODE_AREG]=) 1,
|
|
G ([AMODE_IND]=) 1,
|
|
G ([AMODE_POSTINC]=) 1,
|
|
G ([AMODE_PREDEC]=) 1,
|
|
G ([AMODE_INDOFF]=) 1,
|
|
G ([AMODE_ABS]=) 0,
|
|
G ([AMODE_INDIX]=) 1
|
|
};
|
|
|
|
|
|
static const int amode_operands[] =
|
|
{
|
|
G ([AMODE_NONE]=) 0,
|
|
G ([AMODE_IMM]=) 1,
|
|
G ([AMODE_REG]=) 1,
|
|
G ([AMODE_AREG]=) 1,
|
|
G ([AMODE_IND]=) 1,
|
|
G ([AMODE_POSTINC]=) 1,
|
|
G ([AMODE_PREDEC]=) 1,
|
|
G ([AMODE_INDOFF]=) 2,
|
|
G ([AMODE_ABS]=) 1,
|
|
G ([AMODE_INDIX]=) 1
|
|
};
|
|
|
|
|
|
static const char *amode_name[] =
|
|
{
|
|
G ([AMODE_NONE]=) "",
|
|
G ([AMODE_IMM]=) "imm",
|
|
G ([AMODE_REG]=) "reg",
|
|
G ([AMODE_AREG]=) "reg",
|
|
G ([AMODE_IND]=) "ind",
|
|
G ([AMODE_POSTINC]=) "postinc",
|
|
G ([AMODE_PREDEC]=) "predec",
|
|
G ([AMODE_INDOFF]=) "indoff",
|
|
G ([AMODE_ABS]=) "abs",
|
|
G ([AMODE_INDIX]=) "indix",
|
|
};
|
|
|
|
|
|
static const value_mapping_t swap_map_for_size[] =
|
|
{
|
|
-1,
|
|
G ([B]=) MAP_NATIVE,
|
|
G ([W]=) MAP_SWAP16,
|
|
-1,
|
|
G ([L]=) MAP_SWAP32
|
|
};
|
|
|
|
|
|
static const char char_for_size[] =
|
|
{
|
|
'?',
|
|
G ([B]=) 'b',
|
|
G ([W]=) 'w',
|
|
'?',
|
|
G ([L]=) 'l'
|
|
};
|
|
|
|
#undef G
|
|
|
|
|
|
static guest_code_descriptor_t *
|
|
process_unary (const xlate_descriptor_t *x)
|
|
{
|
|
guest_code_descriptor_t *retval;
|
|
BOOL bitwise_p, compare_p;
|
|
size_mask_t size;
|
|
char buf[1024]; /* For scratch stuff. */
|
|
|
|
/* Note if this operation is a bitwise one. If so, we have a little
|
|
* more freedom doing things to memory when we don't need the N bit.
|
|
* These ops are assumed to clear CV and set NZ appropriately.
|
|
*/
|
|
bitwise_p = bitwise_op_p (x->i386_op);
|
|
|
|
/* Is it a compare? If so, we don't write any result value back. */
|
|
compare_p = compare_op_p (x->i386_op);
|
|
|
|
retval = NULL;
|
|
|
|
for (size = B; size <= L; size <<= 1)
|
|
if (x->sizes & size)
|
|
{
|
|
int dst_amode = x->value[0].amode;
|
|
guest_code_descriptor_t def, *with_cc, *without_cc;
|
|
|
|
if (dst_amode == AMODE_AREG && size == B)
|
|
abort ();
|
|
|
|
/* Compute some reasonable defaults. */
|
|
default_desc (x, size, &def);
|
|
|
|
/* We have two cases: computing cc's, and not computing cc's. */
|
|
with_cc = alloc_gcd ();
|
|
without_cc = alloc_gcd ();
|
|
*with_cc = *without_cc = def; /* Set to default values. */
|
|
|
|
/* Insert them into the chain. */
|
|
with_cc->next = retval;
|
|
without_cc->next = with_cc;
|
|
retval = without_cc;
|
|
|
|
/* Set up their names and special attributes. */
|
|
with_cc->static_p = TRUE;
|
|
without_cc->name = create_name (x->name, size,
|
|
AMODE_NONE, dst_amode, 0);
|
|
with_cc->name = create_name (x->name, size,
|
|
AMODE_NONE, dst_amode, 1);
|
|
|
|
if (bitwise_p || compare_p)
|
|
{
|
|
/* We still get CVZ for free when we do a bitwise op, regardless
|
|
* of endianness. We also get CVZ for free when doing tst
|
|
* instructions, since they always clear C and V (this wouldn't
|
|
* work for general compares).
|
|
*/
|
|
if (size == B)
|
|
without_cc->cc_out = M68K_CC_CNVZ;
|
|
else
|
|
without_cc->cc_out = (M68K_CCC | M68K_CCV | M68K_CCZ);
|
|
|
|
if (REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
/* For not/tst when we don't need the N bit, we can
|
|
* tolerate a wider range of initial mappings, since
|
|
* bit order is irrelevant.
|
|
*/
|
|
if (size == W)
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= MAP_NATIVE_MASK | MAP_SWAP16_MASK;
|
|
else if (size == L)
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= MAP_NATIVE_MASK | MAP_SWAP16_MASK | MAP_SWAP32_MASK;
|
|
|
|
without_cc->reg_op_info[0].output_state
|
|
= compare_p ? ROS_UNTOUCHED : ROS_UNTOUCHED_DIRTY;
|
|
}
|
|
}
|
|
else
|
|
without_cc->cc_out = M68K_CCZ;
|
|
|
|
/* See if we need a scratch register. */
|
|
if (MEMORY_AMODE_P (dst_amode) && size != B)
|
|
{
|
|
with_cc->scratch_reg = REGSET_ALL;
|
|
if (!bitwise_p && !compare_p)
|
|
without_cc->scratch_reg = REGSET_ALL;
|
|
}
|
|
|
|
if (REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
if (!strcmp (x->i386_op, "test"))
|
|
{
|
|
sprintf (buf, "i386_test%c_reg_reg", char_for_size[size]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order.operand_num[0]
|
|
= with_cc->compile_func[0].order.operand_num[1]
|
|
= x->value[0].operand_num[0];
|
|
}
|
|
else if (!strcmp (x->i386_op, "not"))
|
|
{
|
|
/* Use "xorl %-1,%eax" instead of "notl %eax"; not only
|
|
* does this give us CC bits, it's also UV pairable
|
|
* where not is not pairable at all.
|
|
*/
|
|
sprintf (buf, "i386_xor%c_imm_reg", char_for_size[size]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order.operand_num[0]
|
|
= USE_MINUS_ONE;
|
|
with_cc->compile_func[0].order.operand_num[1]
|
|
= x->value[0].operand_num[0];
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "i386_%s%c_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order.operand_num[0]
|
|
= x->value[0].operand_num[0];
|
|
}
|
|
with_cc->reg_op_info[0].output_state
|
|
= without_cc->reg_op_info[0].output_state
|
|
= compare_p ? ROS_UNTOUCHED : ROS_UNTOUCHED_DIRTY;
|
|
|
|
memcpy (&without_cc->compile_func, &with_cc->compile_func,
|
|
sizeof without_cc->compile_func);
|
|
}
|
|
else if (MEMORY_AMODE_P (dst_amode))
|
|
{
|
|
if (size == B)
|
|
{
|
|
if (!strcmp (x->i386_op, "test"))
|
|
{
|
|
sprintf (buf, "host_cmpb_imm_%s",
|
|
amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order.operand_num[0] = USE_ZERO;
|
|
with_cc->compile_func[0].order.operand_num[1]
|
|
= x->value[0].operand_num[0];
|
|
with_cc->compile_func[0].order.operand_num[2]
|
|
= x->value[0].operand_num[1];
|
|
}
|
|
else if (!strcmp (x->i386_op, "not"))
|
|
{
|
|
/* Use "xorl %-1,mem" instead of "notl mem"; not only
|
|
* does this give us CC bits, it's also UV pairable
|
|
* where "not" is not pairable at all.
|
|
*/
|
|
sprintf (buf, "host_xorb_imm_%s",
|
|
amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order.operand_num[0]
|
|
= USE_MINUS_ONE;
|
|
with_cc->compile_func[0].order.operand_num[1]
|
|
= x->value[0].operand_num[0];
|
|
with_cc->compile_func[0].order.operand_num[2]
|
|
= x->value[0].operand_num[1];
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "host_%sb_%s",
|
|
x->i386_op, amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order.operand_num[0]
|
|
= x->value[0].operand_num[0];
|
|
with_cc->compile_func[0].order.operand_num[1]
|
|
= x->value[0].operand_num[1];
|
|
}
|
|
|
|
memcpy (&without_cc->compile_func, &with_cc->compile_func,
|
|
sizeof without_cc->compile_func);
|
|
}
|
|
else /* size != B */
|
|
{
|
|
assert (with_cc->scratch_reg != 0);
|
|
|
|
/* First load up the value into a scratch reg. */
|
|
sprintf (buf, "host_move%c_%s_reg_swap",
|
|
char_for_size[size],
|
|
/* don't offset twice. */
|
|
(dst_amode == AMODE_POSTINC && !compare_p)
|
|
? "ind" : amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
|
|
/* Get up to two operands needed to specify the
|
|
* source address. We may overwrite the second
|
|
* operand below if it's not used.
|
|
*/
|
|
with_cc->compile_func[0].order.operand_num[0] =
|
|
x->value[0].operand_num[0];
|
|
with_cc->compile_func[0].order.operand_num[1] =
|
|
x->value[0].operand_num[1];
|
|
|
|
/* Put the destination scratch register in the
|
|
* appropriate operand (either 1 or 2).
|
|
*/
|
|
with_cc->compile_func[0].order
|
|
.operand_num[amode_operands[dst_amode]]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Now perform the operation. */
|
|
if (!strcmp (x->i386_op, "test"))
|
|
{
|
|
sprintf (buf, "i386_test%c_reg_reg", char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= with_cc->compile_func[1].order.operand_num[1]
|
|
= USE_SCRATCH_REG;
|
|
}
|
|
else if (!strcmp (x->i386_op, "not"))
|
|
{
|
|
/* Use "xorl %-1,%eax" instead of "notl %eax"; not only
|
|
* does this give us CC bits, it's also UV pairable
|
|
* where not is not pairable at all.
|
|
*/
|
|
sprintf (buf, "i386_xor%c_imm_reg", char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= USE_MINUS_ONE;
|
|
with_cc->compile_func[1].order.operand_num[1]
|
|
= USE_SCRATCH_REG;
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "i386_%s%c_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
}
|
|
|
|
/* If it's not a compare, swap the value and write it back. */
|
|
if (!compare_p)
|
|
{
|
|
sprintf (buf, "host_move%c_reg_%s_swap",
|
|
char_for_size[size],
|
|
/* don't offset twice. */
|
|
(dst_amode == AMODE_PREDEC)
|
|
? "ind" : amode_name[dst_amode]);
|
|
with_cc->compile_func[2].func = strdup (buf);
|
|
with_cc->compile_func[2].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
with_cc->compile_func[2].order.operand_num[1]
|
|
= x->value[0].operand_num[0];
|
|
with_cc->compile_func[2].order.operand_num[2]
|
|
= x->value[0].operand_num[1];
|
|
}
|
|
|
|
if (compare_p || bitwise_p)
|
|
{
|
|
assert (!compare_p || !strcmp (x->i386_op, "test"));
|
|
assert (!bitwise_p || !strcmp (x->i386_op, "not"));
|
|
|
|
sprintf (buf, "host_%s%c_imm_%s",
|
|
compare_p ? "cmp" : "xor",
|
|
char_for_size[size], amode_name[dst_amode]);
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
without_cc->compile_func[0].order.operand_num[0]
|
|
= compare_p ? USE_ZERO : USE_MINUS_ONE;
|
|
without_cc->compile_func[0].order.operand_num[1]
|
|
= x->value[0].operand_num[0];
|
|
without_cc->compile_func[0].order.operand_num[2]
|
|
= x->value[0].operand_num[1];
|
|
}
|
|
else
|
|
{
|
|
/* without_cc looks the same as the with_cc case. */
|
|
memcpy (without_cc->compile_func,
|
|
with_cc->compile_func,
|
|
sizeof without_cc->compile_func);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
abort ();
|
|
|
|
assert (!(with_cc->cc_out & ~x->cc_to_compute));
|
|
assert (!(without_cc->cc_out & ~x->cc_to_compute));
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/* Binary ops can have at most one operand in memory. Supported binary ops:
|
|
* Bitwise: and, or, xor
|
|
* Arithmetic: add, sub
|
|
* Compare: cmp
|
|
* Shift: lsl, asr, lsr (not all cc bits computed)
|
|
*/
|
|
static guest_code_descriptor_t *
|
|
process_binary (const xlate_descriptor_t *x)
|
|
{
|
|
guest_code_descriptor_t *retval;
|
|
BOOL bitwise_p, compare_p, shift_p;
|
|
size_mask_t size;
|
|
char buf[1024]; /* For scratch stuff. */
|
|
|
|
/* Note if this operation is a bitwise one. If so, we have a little
|
|
* more freedom doing things to memory when we don't need the N bit.
|
|
* These ops are assumed to clear CV and set NZ appropriately.
|
|
*/
|
|
bitwise_p = bitwise_op_p (x->i386_op);
|
|
|
|
/* Is it a compare? If so, we don't write any result value back. */
|
|
compare_p = compare_op_p (x->i386_op);
|
|
|
|
/* Is it a shift? */
|
|
shift_p = shift_op_p (x->i386_op);
|
|
|
|
retval = NULL;
|
|
|
|
for (size = B; size <= L; size <<= 1)
|
|
if (x->sizes & size)
|
|
{
|
|
int src_amode = x->value[0].amode;
|
|
int dst_amode = x->value[MAX_XLATE_VALUES - 1].amode;
|
|
guest_code_descriptor_t def, *with_cc, *without_cc;
|
|
|
|
/* Compute some reasonable defaults. */
|
|
default_desc (x, size, &def);
|
|
|
|
/* We have two cases: computing cc's, and not computing cc's. */
|
|
with_cc = alloc_gcd ();
|
|
without_cc = alloc_gcd ();
|
|
*with_cc = *without_cc = def; /* Set to default values. */
|
|
|
|
/* Insert them into the chain. */
|
|
with_cc->next = retval;
|
|
without_cc->next = with_cc;
|
|
retval = without_cc;
|
|
|
|
/* Set up their names and special attributes. */
|
|
with_cc->static_p = TRUE;
|
|
without_cc->name = create_name (x->name, size,
|
|
src_amode, dst_amode, 0);
|
|
with_cc->name = create_name (x->name, size,
|
|
src_amode, dst_amode, 1);
|
|
|
|
if (bitwise_p || compare_p)
|
|
{
|
|
/* We still get CVZ for free when we do a bitwise op, regardless
|
|
* of endianness. We get Z for free for compares.
|
|
*/
|
|
if (size == B)
|
|
without_cc->cc_out = M68K_CC_CNVZ;
|
|
else if (bitwise_p)
|
|
without_cc->cc_out = (M68K_CCC | M68K_CCV | M68K_CCZ);
|
|
else
|
|
without_cc->cc_out = M68K_CCZ;
|
|
|
|
if (bitwise_p
|
|
&& src_amode == AMODE_IMM
|
|
&& REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
/* For bitwise ops when we don't need the N bit, we can
|
|
* tolerate a wider range of initial mappings.
|
|
*/
|
|
if (size != L)
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= MAP_NATIVE_MASK | MAP_SWAP16_MASK;
|
|
else
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= MAP_NATIVE_MASK | MAP_SWAP16_MASK | MAP_SWAP32_MASK;
|
|
without_cc->reg_op_info[0].output_state = ROS_UNTOUCHED_DIRTY;
|
|
|
|
}
|
|
else if (REGISTER_AMODE_P (src_amode)
|
|
&& MEMORY_AMODE_P (dst_amode))
|
|
{
|
|
if (size == W)
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= MAP_SWAP16_MASK;
|
|
else if (size == L)
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= MAP_SWAP32_MASK;
|
|
}
|
|
else if (MEMORY_AMODE_P (src_amode)
|
|
&& REGISTER_AMODE_P (dst_amode)
|
|
&& (dst_amode != AMODE_AREG || size != W)
|
|
&& size != B)
|
|
{
|
|
without_cc->reg_op_info[amode_reg_operands[src_amode]]
|
|
.acceptable_mapping = ((size == W)
|
|
? MAP_SWAP16_MASK
|
|
: MAP_SWAP32_MASK);
|
|
|
|
if (compare_p)
|
|
without_cc->reg_op_info[amode_reg_operands[src_amode]]
|
|
.output_state = ROS_UNTOUCHED;
|
|
else
|
|
without_cc->reg_op_info[amode_reg_operands[src_amode]]
|
|
.output_state = ROS_UNTOUCHED_DIRTY;
|
|
}
|
|
}
|
|
else
|
|
without_cc->cc_out = M68K_CC_NONE;
|
|
|
|
/* For add, sub, and cmp with immediate constants, we allow
|
|
* offset registers, but only when we don't care about
|
|
* the overflow bit.
|
|
*/
|
|
if (src_amode == AMODE_IMM
|
|
&& REGISTER_AMODE_P (dst_amode)
|
|
&& !bitwise_p
|
|
&& !shift_p)
|
|
{
|
|
without_cc->reg_op_info[0].acceptable_mapping
|
|
= (MAP_NATIVE_MASK | MAP_OFFSET_MASK);
|
|
without_cc->cc_out = ((M68K_CCC | M68K_CCN | M68K_CCX | M68K_CCZ)
|
|
& x->cc_to_compute);
|
|
}
|
|
|
|
/* add/sub to address register never affect cc bits. */
|
|
if (dst_amode == AMODE_AREG && !compare_p)
|
|
{
|
|
if (bitwise_p)
|
|
abort ();
|
|
with_cc->cc_out = without_cc->cc_out = M68K_CC_NONE;
|
|
}
|
|
|
|
/* See if we need a scratch register. */
|
|
if (((MEMORY_AMODE_P (src_amode) || MEMORY_AMODE_P (dst_amode))
|
|
&& size != B)
|
|
|| (size == W
|
|
&& dst_amode == AMODE_AREG
|
|
&& src_amode != AMODE_IMM))
|
|
{
|
|
with_cc->scratch_reg = REGSET_ALL;
|
|
if ((size == W && dst_amode == AMODE_AREG)
|
|
|| (!bitwise_p && !compare_p))
|
|
without_cc->scratch_reg = REGSET_ALL;
|
|
}
|
|
|
|
/* Compares do not affect the destination register, unlike other
|
|
* binary ops.
|
|
*/
|
|
if (compare_p && REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
without_cc->reg_op_info[amode_reg_operands[src_amode]]
|
|
.output_state = ROS_UNTOUCHED;
|
|
with_cc->reg_op_info[amode_reg_operands[src_amode]]
|
|
.output_state = ROS_UNTOUCHED;
|
|
}
|
|
|
|
if (src_amode == AMODE_IMM)
|
|
{
|
|
if (REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
if (bitwise_p)
|
|
sprintf (buf, "host_%s%c_imm_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
else if (size == L && !shift_p)
|
|
sprintf (buf, "host_%sl_imm_reg", x->i386_op);
|
|
else
|
|
sprintf (buf, "i386_%s%c_imm_reg", x->i386_op,
|
|
(dst_amode == AMODE_AREG)
|
|
? 'l' : char_for_size[size]);
|
|
with_cc->compile_func[0].func =
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
else if (MEMORY_AMODE_P (dst_amode))
|
|
{
|
|
if (size == B)
|
|
{
|
|
sprintf (buf, "host_%s%c_imm_%s",
|
|
x->i386_op,
|
|
char_for_size[size], amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func =
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
else /* size != B */
|
|
{
|
|
/* First load up the value into a scratch reg. */
|
|
sprintf (buf, "host_move%c_%s_reg_swap",
|
|
char_for_size[size],
|
|
/* don't offset twice. */
|
|
(dst_amode == AMODE_POSTINC && !compare_p)
|
|
? "ind" : amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
|
|
/* Get up to two operands needed to specify the
|
|
* source address. We may overwrite the second
|
|
* operand below if it's not used.
|
|
*/
|
|
with_cc->compile_func[0].order.operand_num[0] =
|
|
x->value[1].operand_num[0];
|
|
with_cc->compile_func[0].order.operand_num[1] =
|
|
x->value[1].operand_num[1];
|
|
|
|
/* Put the destination scratch register in the
|
|
* appropriate operand (either 1 or 2).
|
|
*/
|
|
with_cc->compile_func[0].order
|
|
.operand_num[amode_operands[dst_amode]]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Now perform the operation. */
|
|
sprintf (buf, "i386_%s%c_imm_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= x->value[0].operand_num[0];
|
|
with_cc->compile_func[1].order.operand_num[1]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* If it's not a compare, swap the value and write
|
|
* it back.
|
|
*/
|
|
if (!compare_p)
|
|
{
|
|
sprintf (buf, "host_move%c_reg_%s_swap",
|
|
char_for_size[size],
|
|
/* don't offset twice. */
|
|
(dst_amode == AMODE_PREDEC)
|
|
? "ind" : amode_name[dst_amode]);
|
|
with_cc->compile_func[2].func = strdup (buf);
|
|
with_cc->compile_func[2].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
/* defaults are acceptable for other operand_nums. */
|
|
}
|
|
|
|
if (!bitwise_p && !compare_p)
|
|
{
|
|
/* without_cc looks the same as the with_cc case. */
|
|
memcpy (without_cc->compile_func,
|
|
with_cc->compile_func,
|
|
sizeof without_cc->compile_func);
|
|
}
|
|
else /* SIZE != B && bitwise_p */
|
|
{
|
|
/* When we don't need the N bit we can do a much
|
|
* faster sequence for bitwise ops. For example,
|
|
* when the 68k program says: "andw #0x001F,_addr"
|
|
* we say (in i386-speak) "andw $0x1F00,_addr"
|
|
* When we are doing a cmp and only need the Z bit,
|
|
* we can just cmp with the swapped value.
|
|
*/
|
|
sprintf (buf, "host_%s%c_imm_%s",
|
|
x->i386_op, char_for_size[size],
|
|
amode_name[dst_amode]);
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
without_cc->compile_func[1].func = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
abort ();
|
|
}
|
|
else if (REGISTER_AMODE_P (src_amode))
|
|
{
|
|
if (dst_amode == AMODE_AREG && size == W)
|
|
{
|
|
assert (with_cc->scratch_reg);
|
|
assert (without_cc->scratch_reg);
|
|
|
|
/* Sign extend the source register to a long. */
|
|
with_cc->compile_func[0].func =
|
|
without_cc->compile_func[0].func = "i386_movswl_reg_reg";
|
|
with_cc->compile_func[0].order.operand_num[1]
|
|
= without_cc->compile_func[0].order.operand_num[1]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Perform the actual operation. */
|
|
sprintf (buf, "i386_%sl_reg_reg", x->i386_op);
|
|
with_cc->compile_func[1].func =
|
|
without_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0] =
|
|
without_cc->compile_func[1].order.operand_num[0] =
|
|
USE_SCRATCH_REG;
|
|
}
|
|
else if (REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
sprintf (buf, "i386_%s%c_reg_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
with_cc->compile_func[0].func =
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
else if (MEMORY_AMODE_P (dst_amode))
|
|
{
|
|
if (size == B)
|
|
{
|
|
sprintf (buf, "host_%sb_reg_%s",
|
|
x->i386_op, amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func =
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
else
|
|
{
|
|
/* First load up the value into a scratch reg. */
|
|
sprintf (buf, "host_move%c_%s_reg_swap",
|
|
char_for_size[size],
|
|
/* don't offset twice. */
|
|
(dst_amode == AMODE_POSTINC && !compare_p)
|
|
? "ind" : amode_name[dst_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
|
|
/* Get up to two operands needed to specify the
|
|
* source address. We may overwrite the second
|
|
* operand below if it's not used.
|
|
*/
|
|
with_cc->compile_func[0].order.operand_num[0] =
|
|
x->value[1].operand_num[0];
|
|
with_cc->compile_func[0].order.operand_num[1] =
|
|
x->value[1].operand_num[1];
|
|
|
|
/* Put the destination scratch register in the
|
|
* appropriate operand (either 1 or 2).
|
|
*/
|
|
with_cc->compile_func[0].order
|
|
.operand_num[amode_operands[dst_amode]]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Now perform the operation. */
|
|
sprintf (buf, "i386_%s%c_reg_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= x->value[0].operand_num[0];
|
|
with_cc->compile_func[1].order.operand_num[1]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* If it's not a compare, swap the value and write
|
|
* it back.
|
|
*/
|
|
if (!compare_p)
|
|
{
|
|
sprintf (buf, "host_move%c_reg_%s_swap",
|
|
char_for_size[size],
|
|
/* don't offset twice. */
|
|
(dst_amode == AMODE_PREDEC)
|
|
? "ind" : amode_name[dst_amode]);
|
|
with_cc->compile_func[2].func = strdup (buf);
|
|
with_cc->compile_func[2].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
/* defaults are acceptable for other operand_nums. */
|
|
}
|
|
|
|
if (!bitwise_p && !compare_p)
|
|
{
|
|
/* without_cc looks the same as the with_cc case. */
|
|
memcpy (without_cc->compile_func,
|
|
with_cc->compile_func,
|
|
sizeof without_cc->compile_func);
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "host_%s%c_reg_%s",
|
|
x->i386_op, char_for_size[size],
|
|
amode_name[dst_amode]);
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
abort ();
|
|
}
|
|
else if (MEMORY_AMODE_P (src_amode))
|
|
{
|
|
/* Destination must be a register. */
|
|
if (!REGISTER_AMODE_P (dst_amode))
|
|
abort ();
|
|
|
|
if (size == W && dst_amode == AMODE_AREG)
|
|
{
|
|
assert (with_cc->scratch_reg);
|
|
assert (without_cc->scratch_reg);
|
|
|
|
/* First load up the value into a scratch reg. */
|
|
sprintf (buf, "host_move%c_%s_reg_swap",
|
|
char_for_size[size], amode_name[src_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order
|
|
.operand_num[amode_operands[src_amode]]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Sign extend the scratch value. */
|
|
with_cc->compile_func[1].func
|
|
= "i386_movswl_reg_reg";
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
with_cc->compile_func[1].order.operand_num[1]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Now perform the operation. We need to call the
|
|
* host version here if the src was a predec or a postinc,
|
|
* since that might have offset the register to which
|
|
* we are comparing, e.g. "cmpw a0@-,a0".
|
|
*/
|
|
sprintf (buf, "%s_%sl_reg_reg",
|
|
((src_amode == AMODE_PREDEC
|
|
|| src_amode == AMODE_POSTINC)
|
|
? "host" : "i386"),
|
|
x->i386_op);
|
|
with_cc->compile_func[2].func = strdup (buf);
|
|
with_cc->compile_func[2].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
with_cc->compile_func[2].order.operand_num[1]
|
|
= x->value[1].operand_num[0];
|
|
|
|
/* Make with and without cases identical. */
|
|
memcpy (&without_cc->compile_func,
|
|
&with_cc->compile_func,
|
|
sizeof with_cc->compile_func);
|
|
}
|
|
else if (size == B)
|
|
{
|
|
/* Since it's only a byte op, we don't need a temp
|
|
* register.
|
|
*/
|
|
sprintf (buf, "host_%sb_%s_reg",
|
|
x->i386_op, amode_name[src_amode]);
|
|
without_cc->compile_func[0].func
|
|
= with_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
else
|
|
{
|
|
assert (with_cc->scratch_reg);
|
|
|
|
/* First load up the value into a scratch reg. */
|
|
sprintf (buf, "host_move%c_%s_reg_swap",
|
|
char_for_size[size], amode_name[src_amode]);
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
with_cc->compile_func[0].order
|
|
.operand_num[amode_operands[src_amode]]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Now perform the operation. */
|
|
if (size == L
|
|
&& dst_amode == AMODE_AREG
|
|
&& (src_amode == AMODE_PREDEC
|
|
|| src_amode == AMODE_POSTINC)
|
|
&& (!strcmp (x->i386_op, "add")
|
|
|| !strcmp (x->i386_op, "sub")
|
|
|| !strcmp (x->i386_op, "cmp")))
|
|
{
|
|
sprintf (buf, "host_%sl_reg_reg", x->i386_op);
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "i386_%s%c_reg_reg", x->i386_op,
|
|
char_for_size[size]);
|
|
}
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
with_cc->compile_func[1].order.operand_num[1]
|
|
= x->value[1].operand_num[0];
|
|
|
|
if (!bitwise_p && !compare_p)
|
|
{
|
|
assert (without_cc->scratch_reg);
|
|
|
|
/* without_cc looks the same as the with_cc case. */
|
|
memcpy (without_cc->compile_func,
|
|
with_cc->compile_func,
|
|
sizeof without_cc->compile_func);
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "host_%s%c_%s_reg",
|
|
x->i386_op, char_for_size[size],
|
|
amode_name[src_amode]);
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
abort ();
|
|
|
|
assert (!(with_cc->cc_out & ~x->cc_to_compute));
|
|
assert (!(without_cc->cc_out & ~x->cc_to_compute));
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
static guest_code_descriptor_t *
|
|
process_move (const xlate_descriptor_t *x)
|
|
{
|
|
guest_code_descriptor_t *retval;
|
|
int i, op;
|
|
size_mask_t size;
|
|
char buf[1024]; /* For scratch stuff. */
|
|
|
|
retval = NULL;
|
|
for (size = B; size <= L; size <<= 1)
|
|
if (x->sizes & size)
|
|
{
|
|
int src_amode = x->value[0].amode;
|
|
int dst_amode = x->value[MAX_XLATE_VALUES - 1].amode;
|
|
guest_code_descriptor_t def, *with_cc, *without_cc;
|
|
|
|
default_desc (x, size, &def);
|
|
|
|
for (i = 0, op = 0; i < MAX_XLATE_VALUES; i++)
|
|
{
|
|
int amode;
|
|
reg_operand_info_t *r;
|
|
|
|
amode = x->value[i].amode;
|
|
if (amode_reg_operands[amode] == 0)
|
|
continue;
|
|
|
|
r = &def.reg_op_info[op];
|
|
|
|
/* See if the destination value is a register. */
|
|
if (i == MAX_XLATE_VALUES - 1 && REGISTER_AMODE_P (amode))
|
|
{
|
|
/* Figure out which byte orders are acceptable. */
|
|
if (size == B)
|
|
r->acceptable_mapping = MAP_NATIVE_MASK;
|
|
else if (size == W && amode != AMODE_AREG)
|
|
r->acceptable_mapping =
|
|
(MAP_NATIVE_MASK | MAP_SWAP16_MASK);
|
|
else /* size == L */
|
|
r->acceptable_mapping = MAP_ALL_MASK;
|
|
|
|
/* Figure out if we can just use a spare reg. */
|
|
if (size == L || amode == AMODE_AREG)
|
|
r->request_type = REQUEST_SPARE_REG;
|
|
|
|
/* Compute a likely default for the register
|
|
* output state.
|
|
*/
|
|
if (size == B || !MEMORY_AMODE_P (src_amode)
|
|
|| (size == W && dst_amode == AMODE_AREG))
|
|
r->output_state = ROS_NATIVE_DIRTY;
|
|
else if (size == W)
|
|
r->output_state = ROS_SWAP16_DIRTY;
|
|
else if (size == L)
|
|
r->output_state = ROS_SWAP32_DIRTY;
|
|
}
|
|
else if (i == 0 /* Source operand for reg->mem? */
|
|
&& REGISTER_AMODE_P (amode)
|
|
&& MEMORY_AMODE_P (dst_amode))
|
|
{
|
|
/* Force it to come in swapped. */
|
|
r->acceptable_mapping = 1L << swap_map_for_size[size];
|
|
}
|
|
else if (i == 0 /* Source operand for reg->reg? */
|
|
&& REGISTER_AMODE_P (amode)
|
|
&& REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
r->acceptable_mapping = MAP_NATIVE_MASK;
|
|
}
|
|
else /* Generic memory operand (address register). */
|
|
{
|
|
r->acceptable_mapping = MAP_NATIVE_MASK | MAP_OFFSET_MASK;
|
|
}
|
|
op++;
|
|
}
|
|
|
|
/* We have two cases: computing cc's, and not computing cc's. */
|
|
with_cc = alloc_gcd ();
|
|
without_cc = alloc_gcd ();
|
|
*with_cc = *without_cc = def; /* Set to default values. */
|
|
|
|
/* Insert them into the chain. */
|
|
with_cc->next = retval;
|
|
without_cc->next = with_cc;
|
|
retval = without_cc;
|
|
|
|
/* Set up their names and special attributes. */
|
|
without_cc->cc_out = M68K_CC_NONE;
|
|
with_cc->static_p = TRUE;
|
|
without_cc->name = create_name (x->name, size,
|
|
src_amode, dst_amode, 0);
|
|
with_cc->name = create_name (x->name, size,
|
|
src_amode, dst_amode, 1);
|
|
|
|
/* Memory->memory moves require a scratch register. imm->memory
|
|
* moves require a scratch register if we're interested in CC bits.
|
|
* *->memory moves require a scratch register if we need cc bits
|
|
* and we're not doing a byte move. If we're doing a non-byte
|
|
* move from memory to a register and we want cc's then we also
|
|
* need a scratch register. We will demand REGSET_BYTE for the
|
|
* with_cc case, because if they don't want the Z bit we can compute
|
|
* CNV by testing the low byte of the temporary swapped value
|
|
* (instead of swapping the value, testing, and swapping it back).
|
|
*/
|
|
if ((MEMORY_AMODE_P (dst_amode)
|
|
&& (MEMORY_AMODE_P (src_amode)
|
|
|| src_amode == AMODE_IMM
|
|
|| size != B))
|
|
|| (MEMORY_AMODE_P (src_amode) && size != B)
|
|
|| src_amode == AMODE_INDIX
|
|
|| dst_amode == AMODE_INDIX)
|
|
{
|
|
with_cc->scratch_reg = REGSET_BYTE;
|
|
}
|
|
if ((MEMORY_AMODE_P (src_amode) && MEMORY_AMODE_P (dst_amode))
|
|
|| src_amode == AMODE_INDIX || dst_amode == AMODE_INDIX)
|
|
without_cc->scratch_reg = (size == B) ? REGSET_BYTE : REGSET_ALL;
|
|
|
|
if (src_amode == AMODE_IMM)
|
|
{
|
|
if (size != B && MEMORY_AMODE_P (dst_amode))
|
|
assert (with_cc->scratch_reg);
|
|
|
|
/* e.g. movw #5,d0, movl #19,a0@+ */
|
|
sprintf (buf, "host_move%c_imm_%s",
|
|
dst_amode == AMODE_AREG ? 'l' : char_for_size[size],
|
|
amode_name[dst_amode]);
|
|
without_cc->compile_func[0].func =
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
|
|
if (dst_amode == AMODE_INDIX)
|
|
{
|
|
int t = amode_operands[src_amode] + amode_operands[dst_amode];
|
|
without_cc->compile_func[0].order.operand_num[t]
|
|
= with_cc->compile_func[0].order.operand_num[t]
|
|
= ((x->value[0].operand_num[0] == 0)
|
|
? USE_M68K_PC : ((size == L)
|
|
? USE_M68K_PC_PLUS_FOUR
|
|
: USE_M68K_PC_PLUS_TWO));
|
|
}
|
|
}
|
|
else if (REGISTER_AMODE_P (src_amode))
|
|
{
|
|
if (REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
if (dst_amode == AMODE_AREG && size == W)
|
|
{
|
|
without_cc->compile_func[0].func =
|
|
with_cc->compile_func[0].func =
|
|
"i386_movswl_reg_reg";
|
|
with_cc->compile_func[1].func = "host_testl_reg";
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= x->value[1].operand_num[0];
|
|
}
|
|
else
|
|
{
|
|
sprintf (buf, "i386_mov%c_reg_reg", char_for_size[size]);
|
|
without_cc->compile_func[0].func =
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
sprintf (buf, "host_test%c_reg", char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
}
|
|
}
|
|
else /* dst is a memory amode. */
|
|
{
|
|
sprintf (buf, "host_move%c_reg_%s",
|
|
char_for_size[size],
|
|
amode_name[dst_amode]);
|
|
without_cc->compile_func[0].func =
|
|
with_cc->compile_func[0].func = strdup (buf);
|
|
|
|
if (dst_amode == AMODE_INDIX)
|
|
{
|
|
int t = (amode_operands[src_amode]
|
|
+ amode_operands[dst_amode]);
|
|
without_cc->compile_func[0].order.operand_num[t]
|
|
= with_cc->compile_func[0].order.operand_num[t]
|
|
= USE_M68K_PC;
|
|
}
|
|
|
|
sprintf (buf, "host_test%c_swapped_reg", char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
}
|
|
}
|
|
else /* src is a memory amode. */
|
|
{
|
|
if (REGISTER_AMODE_P (dst_amode))
|
|
{
|
|
sprintf (buf, "host_move%c_%s_reg",
|
|
char_for_size[size],
|
|
amode_name[src_amode]);
|
|
|
|
if (src_amode == AMODE_INDIX)
|
|
{
|
|
int t = (amode_operands[src_amode]
|
|
+ amode_operands[dst_amode]);
|
|
without_cc->compile_func[0].order.operand_num[t]
|
|
= with_cc->compile_func[0].order.operand_num[t]
|
|
= USE_M68K_PC;
|
|
}
|
|
|
|
with_cc->compile_func[0].func =
|
|
without_cc->compile_func[0].func = strdup (buf);
|
|
|
|
if (size == W && dst_amode == AMODE_AREG)
|
|
{
|
|
with_cc->compile_func[1].func =
|
|
without_cc->compile_func[1].func =
|
|
"host_swap16_sext_test_reg";
|
|
with_cc->compile_func[1].order.operand_num[0] =
|
|
without_cc->compile_func[1].order.operand_num[0] =
|
|
x->value[1].operand_num[0];
|
|
}
|
|
else
|
|
{
|
|
/* Create a call to the test function. */
|
|
sprintf (buf, "host_test%c_swapped_reg",
|
|
char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
with_cc->compile_func[1].order.operand_num[0] =
|
|
x->value[1].operand_num[0];
|
|
}
|
|
}
|
|
else /* Memory -> memory transfer. */
|
|
{
|
|
compile_func_t comp;
|
|
|
|
assert (with_cc->scratch_reg);
|
|
assert (without_cc->scratch_reg);
|
|
|
|
sprintf (buf, "host_move%c_%s_reg",
|
|
char_for_size[size],
|
|
amode_name[src_amode]);
|
|
|
|
/* Move from memory to a scratch register. */
|
|
comp.func = strdup (buf);
|
|
comp.order = def.compile_func[0].order;
|
|
comp.order.operand_num[amode_operands[src_amode]]
|
|
= USE_SCRATCH_REG;
|
|
if (src_amode == AMODE_INDIX)
|
|
{
|
|
int t = amode_operands[src_amode] + 1;
|
|
comp.order.operand_num[t] = USE_M68K_PC;
|
|
}
|
|
with_cc->compile_func[0] = without_cc->compile_func[0] = comp;
|
|
|
|
/* Test the scratch register, when we need cc bits. */
|
|
sprintf (buf, "host_test%c_swapped_reg", char_for_size[size]);
|
|
with_cc->compile_func[1].func = strdup (buf);
|
|
memset (&with_cc->compile_func[1].order, 0,
|
|
sizeof with_cc->compile_func[1].order);
|
|
with_cc->compile_func[1].order.operand_num[0]
|
|
= USE_SCRATCH_REG;
|
|
|
|
/* Move from scratch register to memory. */
|
|
sprintf (buf, "host_move%c_reg_%s",
|
|
char_for_size[size],
|
|
amode_name[dst_amode]);
|
|
|
|
comp.func = strdup (buf);
|
|
comp.order.operand_num[0] = USE_SCRATCH_REG;
|
|
comp.order.operand_num[1] = x->value[1].operand_num[0];
|
|
comp.order.operand_num[2] = x->value[1].operand_num[1];
|
|
if (dst_amode == AMODE_INDIX)
|
|
{
|
|
int t = 1 + amode_operands[dst_amode];
|
|
|
|
/* We don't know how many m68k words to skip to get
|
|
* to the indix info if it's an ABS amode (could
|
|
* be absw or absl).
|
|
*/
|
|
if (src_amode == AMODE_ABS)
|
|
abort ();
|
|
|
|
if (src_amode == AMODE_INDOFF || src_amode == AMODE_INDIX)
|
|
comp.order.operand_num[t] = USE_M68K_PC_PLUS_TWO;
|
|
else
|
|
comp.order.operand_num[t] = USE_M68K_PC;
|
|
}
|
|
with_cc->compile_func[2] = without_cc->compile_func[1] = comp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
static void
|
|
default_desc (const xlate_descriptor_t *x, size_mask_t size,
|
|
guest_code_descriptor_t *g)
|
|
{
|
|
oporder_t default_oporder;
|
|
int i, j, op;
|
|
|
|
/* Set up a reasonable default order for our operands. */
|
|
memset (&default_oporder, 0, sizeof default_oporder);
|
|
for (i = op = 0; i < 2; i++)
|
|
for (j = 0; j < amode_operands[x->value[i].amode]; j++)
|
|
default_oporder.operand_num[op++] = x->value[i].operand_num[j];
|
|
|
|
/* Set up a default descriptor for all the cases. */
|
|
memset (g, 0, sizeof *g);
|
|
for (i = 0; i < MAX_COMPILE_FUNCS; i++)
|
|
g->compile_func[i].order = default_oporder;
|
|
g->cc_in = M68K_CC_NONE;
|
|
g->cc_out = x->cc_to_compute;
|
|
|
|
for (i = 0, op = 0; i < MAX_XLATE_VALUES; i++)
|
|
{
|
|
int amode;
|
|
reg_operand_info_t *r;
|
|
|
|
amode = x->value[i].amode;
|
|
if (amode_reg_operands[amode] == 0)
|
|
continue;
|
|
|
|
r = &g->reg_op_info[op];
|
|
r->legitimate_p = TRUE;
|
|
r->add8_p = (MEMORY_AMODE_P (amode) || amode == AMODE_AREG);
|
|
if (amode == AMODE_INDOFF)
|
|
r->operand_num = x->value[i].operand_num[1]; /* offset,reg */
|
|
else
|
|
r->operand_num = x->value[i].operand_num[0];
|
|
|
|
if (r->add8_p) /* Address registers can go anywhere. */
|
|
r->regset = REGSET_ALL;
|
|
else
|
|
r->regset = REGSET_BYTE;
|
|
|
|
if (MEMORY_AMODE_P (amode))
|
|
r->acceptable_mapping = MAP_NATIVE_MASK | MAP_OFFSET_MASK;
|
|
else
|
|
r->acceptable_mapping = MAP_NATIVE_MASK;
|
|
r->request_type = REQUEST_REG;
|
|
|
|
/* If we're a destination register, we're dirty. Otherwise,
|
|
* we're untouched.
|
|
*/
|
|
if (i == MAX_XLATE_VALUES - 1 && REGISTER_AMODE_P (amode))
|
|
r->output_state = ROS_NATIVE_DIRTY;
|
|
else
|
|
r->output_state = ROS_UNTOUCHED;
|
|
|
|
op++;
|
|
}
|
|
|
|
g->reg_op_info[op].legitimate_p = FALSE;
|
|
assert (op < NELEM (g->reg_op_info));
|
|
}
|
|
|
|
|
|
static guest_code_descriptor_t *
|
|
alloc_gcd ()
|
|
{
|
|
guest_code_descriptor_t *g;
|
|
g = (guest_code_descriptor_t *)malloc (sizeof *g);
|
|
memset (g, 0, sizeof *g);
|
|
return g;
|
|
}
|
|
|
|
static char *
|
|
create_name (const char *n, int size, int src_amode, int dst_amode, int which)
|
|
{
|
|
char buf[1024];
|
|
char *s;
|
|
|
|
sprintf (buf, "xlate_%s", n);
|
|
#if 0
|
|
if (src_amode != AMODE_NONE)
|
|
sprintf (buf + strlen (buf), "_%s", amode_name[src_amode]);
|
|
if (dst_amode != AMODE_NONE)
|
|
sprintf (buf + strlen (buf), "_%s", amode_name[dst_amode]);
|
|
#endif
|
|
if (which != 0)
|
|
sprintf (buf + strlen (buf), "_N%d", which);
|
|
for (s = buf; *s != '\0'; s++)
|
|
if (*s == '@')
|
|
*s = char_for_size[size];
|
|
return strdup (buf);
|
|
}
|