blob: 0f80888203d3d480b3f53b7cc05f57dda945c6f5 [file] [log] [blame]
/*
* m68k translation
*
* Copyright (c) 2005-2007 CodeSourcery
* Written by Paul Brook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "cpu.h"
#include "disas/disas.h"
#include "exec/exec-all.h"
#include "tcg/tcg-op.h"
#include "qemu/log.h"
#include "qemu/qemu-print.h"
#include "exec/cpu_ldst.h"
#include "exec/translator.h"
#include "exec/helper-proto.h"
#include "exec/helper-gen.h"
#include "trace-tcg.h"
#include "exec/log.h"
#include "fpu/softfloat.h"
//#define DEBUG_DISPATCH 1
#define DEFO32(name, offset) static TCGv QREG_##name;
#define DEFO64(name, offset) static TCGv_i64 QREG_##name;
#include "qregs.def"
#undef DEFO32
#undef DEFO64
static TCGv_i32 cpu_halted;
static TCGv_i32 cpu_exception_index;
static char cpu_reg_names[2 * 8 * 3 + 5 * 4];
static TCGv cpu_dregs[8];
static TCGv cpu_aregs[8];
static TCGv_i64 cpu_macc[4];
#define REG(insn, pos) (((insn) >> (pos)) & 7)
#define DREG(insn, pos) cpu_dregs[REG(insn, pos)]
#define AREG(insn, pos) get_areg(s, REG(insn, pos))
#define MACREG(acc) cpu_macc[acc]
#define QREG_SP get_areg(s, 7)
static TCGv NULL_QREG;
#define IS_NULL_QREG(t) (t == NULL_QREG)
/* Used to distinguish stores from bad addressing modes. */
static TCGv store_dummy;
#include "exec/gen-icount.h"
void m68k_tcg_init(void)
{
char *p;
int i;
#define DEFO32(name, offset) \
QREG_##name = tcg_global_mem_new_i32(cpu_env, \
offsetof(CPUM68KState, offset), #name);
#define DEFO64(name, offset) \
QREG_##name = tcg_global_mem_new_i64(cpu_env, \
offsetof(CPUM68KState, offset), #name);
#include "qregs.def"
#undef DEFO32
#undef DEFO64
cpu_halted = tcg_global_mem_new_i32(cpu_env,
-offsetof(M68kCPU, env) +
offsetof(CPUState, halted), "HALTED");
cpu_exception_index = tcg_global_mem_new_i32(cpu_env,
-offsetof(M68kCPU, env) +
offsetof(CPUState, exception_index),
"EXCEPTION");
p = cpu_reg_names;
for (i = 0; i < 8; i++) {
sprintf(p, "D%d", i);
cpu_dregs[i] = tcg_global_mem_new(cpu_env,
offsetof(CPUM68KState, dregs[i]), p);
p += 3;
sprintf(p, "A%d", i);
cpu_aregs[i] = tcg_global_mem_new(cpu_env,
offsetof(CPUM68KState, aregs[i]), p);
p += 3;
}
for (i = 0; i < 4; i++) {
sprintf(p, "ACC%d", i);
cpu_macc[i] = tcg_global_mem_new_i64(cpu_env,
offsetof(CPUM68KState, macc[i]), p);
p += 5;
}
NULL_QREG = tcg_global_mem_new(cpu_env, -4, "NULL");
store_dummy = tcg_global_mem_new(cpu_env, -8, "NULL");
}
/* internal defines */
typedef struct DisasContext {
DisasContextBase base;
CPUM68KState *env;
target_ulong pc;
CCOp cc_op; /* Current CC operation */
int cc_op_synced;
TCGv_i64 mactmp;
int done_mac;
int writeback_mask;
TCGv writeback[8];
#define MAX_TO_RELEASE 8
int release_count;
TCGv release[MAX_TO_RELEASE];
} DisasContext;
static void init_release_array(DisasContext *s)
{
#ifdef CONFIG_DEBUG_TCG
memset(s->release, 0, sizeof(s->release));
#endif
s->release_count = 0;
}
static void do_release(DisasContext *s)
{
int i;
for (i = 0; i < s->release_count; i++) {
tcg_temp_free(s->release[i]);
}
init_release_array(s);
}
static TCGv mark_to_release(DisasContext *s, TCGv tmp)
{
g_assert(s->release_count < MAX_TO_RELEASE);
return s->release[s->release_count++] = tmp;
}
static TCGv get_areg(DisasContext *s, unsigned regno)
{
if (s->writeback_mask & (1 << regno)) {
return s->writeback[regno];
} else {
return cpu_aregs[regno];
}
}
static void delay_set_areg(DisasContext *s, unsigned regno,
TCGv val, bool give_temp)
{
if (s->writeback_mask & (1 << regno)) {
if (give_temp) {
tcg_temp_free(s->writeback[regno]);
s->writeback[regno] = val;
} else {
tcg_gen_mov_i32(s->writeback[regno], val);
}
} else {
s->writeback_mask |= 1 << regno;
if (give_temp) {
s->writeback[regno] = val;
} else {
TCGv tmp = tcg_temp_new();
s->writeback[regno] = tmp;
tcg_gen_mov_i32(tmp, val);
}
}
}
static void do_writebacks(DisasContext *s)
{
unsigned mask = s->writeback_mask;
if (mask) {
s->writeback_mask = 0;
do {
unsigned regno = ctz32(mask);
tcg_gen_mov_i32(cpu_aregs[regno], s->writeback[regno]);
tcg_temp_free(s->writeback[regno]);
mask &= mask - 1;
} while (mask);
}
}
/* is_jmp field values */
#define DISAS_JUMP DISAS_TARGET_0 /* only pc was modified dynamically */
#define DISAS_EXIT DISAS_TARGET_1 /* cpu state was modified dynamically */
#if defined(CONFIG_USER_ONLY)
#define IS_USER(s) 1
#else
#define IS_USER(s) (!(s->base.tb->flags & TB_FLAGS_MSR_S))
#define SFC_INDEX(s) ((s->base.tb->flags & TB_FLAGS_SFC_S) ? \
MMU_KERNEL_IDX : MMU_USER_IDX)
#define DFC_INDEX(s) ((s->base.tb->flags & TB_FLAGS_DFC_S) ? \
MMU_KERNEL_IDX : MMU_USER_IDX)
#endif
typedef void (*disas_proc)(CPUM68KState *env, DisasContext *s, uint16_t insn);
#ifdef DEBUG_DISPATCH
#define DISAS_INSN(name) \
static void real_disas_##name(CPUM68KState *env, DisasContext *s, \
uint16_t insn); \
static void disas_##name(CPUM68KState *env, DisasContext *s, \
uint16_t insn) \
{ \
qemu_log("Dispatch " #name "\n"); \
real_disas_##name(env, s, insn); \
} \
static void real_disas_##name(CPUM68KState *env, DisasContext *s, \
uint16_t insn)
#else
#define DISAS_INSN(name) \
static void disas_##name(CPUM68KState *env, DisasContext *s, \
uint16_t insn)
#endif
static const uint8_t cc_op_live[CC_OP_NB] = {
[CC_OP_DYNAMIC] = CCF_C | CCF_V | CCF_Z | CCF_N | CCF_X,
[CC_OP_FLAGS] = CCF_C | CCF_V | CCF_Z | CCF_N | CCF_X,
[CC_OP_ADDB ... CC_OP_ADDL] = CCF_X | CCF_N | CCF_V,
[CC_OP_SUBB ... CC_OP_SUBL] = CCF_X | CCF_N | CCF_V,
[CC_OP_CMPB ... CC_OP_CMPL] = CCF_X | CCF_N | CCF_V,
[CC_OP_LOGIC] = CCF_X | CCF_N
};
static void set_cc_op(DisasContext *s, CCOp op)
{
CCOp old_op = s->cc_op;
int dead;
if (old_op == op) {
return;
}
s->cc_op = op;
s->cc_op_synced = 0;
/*
* Discard CC computation that will no longer be used.
* Note that X and N are never dead.
*/
dead = cc_op_live[old_op] & ~cc_op_live[op];
if (dead & CCF_C) {
tcg_gen_discard_i32(QREG_CC_C);
}
if (dead & CCF_Z) {
tcg_gen_discard_i32(QREG_CC_Z);
}
if (dead & CCF_V) {
tcg_gen_discard_i32(QREG_CC_V);
}
}
/* Update the CPU env CC_OP state. */
static void update_cc_op(DisasContext *s)
{
if (!s->cc_op_synced) {
s->cc_op_synced = 1;
tcg_gen_movi_i32(QREG_CC_OP, s->cc_op);
}
}
/* Generate a jump to an immediate address. */
static void gen_jmp_im(DisasContext *s, uint32_t dest)
{
update_cc_op(s);
tcg_gen_movi_i32(QREG_PC, dest);
s->base.is_jmp = DISAS_JUMP;
}
/* Generate a jump to the address in qreg DEST. */
static void gen_jmp(DisasContext *s, TCGv dest)
{
update_cc_op(s);
tcg_gen_mov_i32(QREG_PC, dest);
s->base.is_jmp = DISAS_JUMP;
}
static void gen_raise_exception(int nr)
{
TCGv_i32 tmp;
tmp = tcg_const_i32(nr);
gen_helper_raise_exception(cpu_env, tmp);
tcg_temp_free_i32(tmp);
}
static void gen_exception(DisasContext *s, uint32_t dest, int nr)
{
update_cc_op(s);
tcg_gen_movi_i32(QREG_PC, dest);
gen_raise_exception(nr);
s->base.is_jmp = DISAS_NORETURN;
}
static inline void gen_addr_fault(DisasContext *s)
{
gen_exception(s, s->base.pc_next, EXCP_ADDRESS);
}
/*
* Generate a load from the specified address. Narrow values are
* sign extended to full register width.
*/
static inline TCGv gen_load(DisasContext *s, int opsize, TCGv addr,
int sign, int index)
{
TCGv tmp;
tmp = tcg_temp_new_i32();
switch(opsize) {
case OS_BYTE:
if (sign)
tcg_gen_qemu_ld8s(tmp, addr, index);
else
tcg_gen_qemu_ld8u(tmp, addr, index);
break;
case OS_WORD:
if (sign)
tcg_gen_qemu_ld16s(tmp, addr, index);
else
tcg_gen_qemu_ld16u(tmp, addr, index);
break;
case OS_LONG:
tcg_gen_qemu_ld32u(tmp, addr, index);
break;
default:
g_assert_not_reached();
}
return tmp;
}
/* Generate a store. */
static inline void gen_store(DisasContext *s, int opsize, TCGv addr, TCGv val,
int index)
{
switch(opsize) {
case OS_BYTE:
tcg_gen_qemu_st8(val, addr, index);
break;
case OS_WORD:
tcg_gen_qemu_st16(val, addr, index);
break;
case OS_LONG:
tcg_gen_qemu_st32(val, addr, index);
break;
default:
g_assert_not_reached();
}
}
typedef enum {
EA_STORE,
EA_LOADU,
EA_LOADS
} ea_what;
/*
* Generate an unsigned load if VAL is 0 a signed load if val is -1,
* otherwise generate a store.
*/
static TCGv gen_ldst(DisasContext *s, int opsize, TCGv addr, TCGv val,
ea_what what, int index)
{
if (what == EA_STORE) {
gen_store(s, opsize, addr, val, index);
return store_dummy;
} else {
return mark_to_release(s, gen_load(s, opsize, addr,
what == EA_LOADS, index));
}
}
/* Read a 16-bit immediate constant */
static inline uint16_t read_im16(CPUM68KState *env, DisasContext *s)
{
uint16_t im;
im = translator_lduw(env, s->pc);
s->pc += 2;
return im;
}
/* Read an 8-bit immediate constant */
static inline uint8_t read_im8(CPUM68KState *env, DisasContext *s)
{
return read_im16(env, s);
}
/* Read a 32-bit immediate constant. */
static inline uint32_t read_im32(CPUM68KState *env, DisasContext *s)
{
uint32_t im;
im = read_im16(env, s) << 16;
im |= 0xffff & read_im16(env, s);
return im;
}
/* Read a 64-bit immediate constant. */
static inline uint64_t read_im64(CPUM68KState *env, DisasContext *s)
{
uint64_t im;
im = (uint64_t)read_im32(env, s) << 32;
im |= (uint64_t)read_im32(env, s);
return im;
}
/* Calculate and address index. */
static TCGv gen_addr_index(DisasContext *s, uint16_t ext, TCGv tmp)
{
TCGv add;
int scale;
add = (ext & 0x8000) ? AREG(ext, 12) : DREG(ext, 12);
if ((ext & 0x800) == 0) {
tcg_gen_ext16s_i32(tmp, add);
add = tmp;
}
scale = (ext >> 9) & 3;
if (scale != 0) {
tcg_gen_shli_i32(tmp, add, scale);
add = tmp;
}
return add;
}
/*
* Handle a base + index + displacement effective addresss.
* A NULL_QREG base means pc-relative.
*/
static TCGv gen_lea_indexed(CPUM68KState *env, DisasContext *s, TCGv base)
{
uint32_t offset;
uint16_t ext;
TCGv add;
TCGv tmp;
uint32_t bd, od;
offset = s->pc;
ext = read_im16(env, s);
if ((ext & 0x800) == 0 && !m68k_feature(s->env, M68K_FEATURE_WORD_INDEX))
return NULL_QREG;
if (m68k_feature(s->env, M68K_FEATURE_M68000) &&
!m68k_feature(s->env, M68K_FEATURE_SCALED_INDEX)) {
ext &= ~(3 << 9);
}
if (ext & 0x100) {
/* full extension word format */
if (!m68k_feature(s->env, M68K_FEATURE_EXT_FULL))
return NULL_QREG;
if ((ext & 0x30) > 0x10) {
/* base displacement */
if ((ext & 0x30) == 0x20) {
bd = (int16_t)read_im16(env, s);
} else {
bd = read_im32(env, s);
}
} else {
bd = 0;
}
tmp = mark_to_release(s, tcg_temp_new());
if ((ext & 0x44) == 0) {
/* pre-index */
add = gen_addr_index(s, ext, tmp);
} else {
add = NULL_QREG;
}
if ((ext & 0x80) == 0) {
/* base not suppressed */
if (IS_NULL_QREG(base)) {
base = mark_to_release(s, tcg_const_i32(offset + bd));
bd = 0;
}
if (!IS_NULL_QREG(add)) {
tcg_gen_add_i32(tmp, add, base);
add = tmp;
} else {
add = base;
}
}
if (!IS_NULL_QREG(add)) {
if (bd != 0) {
tcg_gen_addi_i32(tmp, add, bd);
add = tmp;
}
} else {
add = mark_to_release(s, tcg_const_i32(bd));
}
if ((ext & 3) != 0) {
/* memory indirect */
base = mark_to_release(s, gen_load(s, OS_LONG, add, 0, IS_USER(s)));
if ((ext & 0x44) == 4) {
add = gen_addr_index(s, ext, tmp);
tcg_gen_add_i32(tmp, add, base);
add = tmp;
} else {
add = base;
}
if ((ext & 3) > 1) {
/* outer displacement */
if ((ext & 3) == 2) {
od = (int16_t)read_im16(env, s);
} else {
od = read_im32(env, s);
}
} else {
od = 0;
}
if (od != 0) {
tcg_gen_addi_i32(tmp, add, od);
add = tmp;
}
}
} else {
/* brief extension word format */
tmp = mark_to_release(s, tcg_temp_new());
add = gen_addr_index(s, ext, tmp);
if (!IS_NULL_QREG(base)) {
tcg_gen_add_i32(tmp, add, base);
if ((int8_t)ext)
tcg_gen_addi_i32(tmp, tmp, (int8_t)ext);
} else {
tcg_gen_addi_i32(tmp, add, offset + (int8_t)ext);
}
add = tmp;
}
return add;
}
/* Sign or zero extend a value. */
static inline void gen_ext(TCGv res, TCGv val, int opsize, int sign)
{
switch (opsize) {
case OS_BYTE:
if (sign) {
tcg_gen_ext8s_i32(res, val);
} else {
tcg_gen_ext8u_i32(res, val);
}
break;
case OS_WORD:
if (sign) {
tcg_gen_ext16s_i32(res, val);
} else {
tcg_gen_ext16u_i32(res, val);
}
break;
case OS_LONG:
tcg_gen_mov_i32(res, val);
break;
default:
g_assert_not_reached();
}
}
/* Evaluate all the CC flags. */
static void gen_flush_flags(DisasContext *s)
{
TCGv t0, t1;
switch (s->cc_op) {
case CC_OP_FLAGS:
return;
case CC_OP_ADDB:
case CC_OP_ADDW:
case CC_OP_ADDL:
tcg_gen_mov_i32(QREG_CC_C, QREG_CC_X);
tcg_gen_mov_i32(QREG_CC_Z, QREG_CC_N);
/* Compute signed overflow for addition. */
t0 = tcg_temp_new();
t1 = tcg_temp_new();
tcg_gen_sub_i32(t0, QREG_CC_N, QREG_CC_V);
gen_ext(t0, t0, s->cc_op - CC_OP_ADDB, 1);
tcg_gen_xor_i32(t1, QREG_CC_N, QREG_CC_V);
tcg_gen_xor_i32(QREG_CC_V, QREG_CC_V, t0);
tcg_temp_free(t0);
tcg_gen_andc_i32(QREG_CC_V, t1, QREG_CC_V);
tcg_temp_free(t1);
break;
case CC_OP_SUBB:
case CC_OP_SUBW:
case CC_OP_SUBL:
tcg_gen_mov_i32(QREG_CC_C, QREG_CC_X);
tcg_gen_mov_i32(QREG_CC_Z, QREG_CC_N);
/* Compute signed overflow for subtraction. */
t0 = tcg_temp_new();
t1 = tcg_temp_new();
tcg_gen_add_i32(t0, QREG_CC_N, QREG_CC_V);
gen_ext(t0, t0, s->cc_op - CC_OP_SUBB, 1);
tcg_gen_xor_i32(t1, QREG_CC_N, t0);
tcg_gen_xor_i32(QREG_CC_V, QREG_CC_V, t0);
tcg_temp_free(t0);
tcg_gen_and_i32(QREG_CC_V, QREG_CC_V, t1);
tcg_temp_free(t1);
break;
case CC_OP_CMPB:
case CC_OP_CMPW:
case CC_OP_CMPL:
tcg_gen_setcond_i32(TCG_COND_LTU, QREG_CC_C, QREG_CC_N, QREG_CC_V);
tcg_gen_sub_i32(QREG_CC_Z, QREG_CC_N, QREG_CC_V);
gen_ext(QREG_CC_Z, QREG_CC_Z, s->cc_op - CC_OP_CMPB, 1);
/* Compute signed overflow for subtraction. */
t0 = tcg_temp_new();
tcg_gen_xor_i32(t0, QREG_CC_Z, QREG_CC_N);
tcg_gen_xor_i32(QREG_CC_V, QREG_CC_V, QREG_CC_N);
tcg_gen_and_i32(QREG_CC_V, QREG_CC_V, t0);
tcg_temp_free(t0);
tcg_gen_mov_i32(QREG_CC_N, QREG_CC_Z);
break;
case CC_OP_LOGIC:
tcg_gen_mov_i32(QREG_CC_Z, QREG_CC_N);
tcg_gen_movi_i32(QREG_CC_C, 0);
tcg_gen_movi_i32(QREG_CC_V, 0);
break;
case CC_OP_DYNAMIC:
gen_helper_flush_flags(cpu_env, QREG_CC_OP);
s->cc_op_synced = 1;
break;
default:
t0 = tcg_const_i32(s->cc_op);
gen_helper_flush_flags(cpu_env, t0);
tcg_temp_free(t0);
s->cc_op_synced = 1;
break;
}
/* Note that flush_flags also assigned to env->cc_op. */
s->cc_op = CC_OP_FLAGS;
}
static inline TCGv gen_extend(DisasContext *s, TCGv val, int opsize, int sign)
{
TCGv tmp;
if (opsize == OS_LONG) {
tmp = val;
} else {
tmp = mark_to_release(s, tcg_temp_new());
gen_ext(tmp, val, opsize, sign);
}
return tmp;
}
static void gen_logic_cc(DisasContext *s, TCGv val, int opsize)
{
gen_ext(QREG_CC_N, val, opsize, 1);
set_cc_op(s, CC_OP_LOGIC);
}
static void gen_update_cc_cmp(DisasContext *s, TCGv dest, TCGv src, int opsize)
{
tcg_gen_mov_i32(QREG_CC_N, dest);
tcg_gen_mov_i32(QREG_CC_V, src);
set_cc_op(s, CC_OP_CMPB + opsize);
}
static void gen_update_cc_add(TCGv dest, TCGv src, int opsize)
{
gen_ext(QREG_CC_N, dest, opsize, 1);
tcg_gen_mov_i32(QREG_CC_V, src);
}
static inline int opsize_bytes(int opsize)
{
switch (opsize) {
case OS_BYTE: return 1;
case OS_WORD: return 2;
case OS_LONG: return 4;
case OS_SINGLE: return 4;
case OS_DOUBLE: return 8;
case OS_EXTENDED: return 12;
case OS_PACKED: return 12;
default:
g_assert_not_reached();
}
}
static inline int insn_opsize(int insn)
{
switch ((insn >> 6) & 3) {
case 0: return OS_BYTE;
case 1: return OS_WORD;
case 2: return OS_LONG;
default:
g_assert_not_reached();
}
}
static inline int ext_opsize(int ext, int pos)
{
switch ((ext >> pos) & 7) {
case 0: return OS_LONG;
case 1: return OS_SINGLE;
case 2: return OS_EXTENDED;
case 3: return OS_PACKED;
case 4: return OS_WORD;
case 5: return OS_DOUBLE;
case 6: return OS_BYTE;
default:
g_assert_not_reached();
}
}
/*
* Assign value to a register. If the width is less than the register width
* only the low part of the register is set.
*/
static void gen_partset_reg(int opsize, TCGv reg, TCGv val)
{
TCGv tmp;
switch (opsize) {
case OS_BYTE:
tcg_gen_andi_i32(reg, reg, 0xffffff00);
tmp = tcg_temp_new();
tcg_gen_ext8u_i32(tmp, val);
tcg_gen_or_i32(reg, reg, tmp);
tcg_temp_free(tmp);
break;
case OS_WORD:
tcg_gen_andi_i32(reg, reg, 0xffff0000);
tmp = tcg_temp_new();
tcg_gen_ext16u_i32(tmp, val);
tcg_gen_or_i32(reg, reg, tmp);
tcg_temp_free(tmp);
break;
case OS_LONG:
case OS_SINGLE:
tcg_gen_mov_i32(reg, val);
break;
default:
g_assert_not_reached();
}
}
/*
* Generate code for an "effective address". Does not adjust the base
* register for autoincrement addressing modes.
*/
static TCGv gen_lea_mode(CPUM68KState *env, DisasContext *s,
int mode, int reg0, int opsize)
{
TCGv reg;
TCGv tmp;
uint16_t ext;
uint32_t offset;
switch (mode) {
case 0: /* Data register direct. */
case 1: /* Address register direct. */
return NULL_QREG;
case 3: /* Indirect postincrement. */
if (opsize == OS_UNSIZED) {
return NULL_QREG;
}
/* fallthru */
case 2: /* Indirect register */
return get_areg(s, reg0);
case 4: /* Indirect predecrememnt. */
if (opsize == OS_UNSIZED) {
return NULL_QREG;
}
reg = get_areg(s, reg0);
tmp = mark_to_release(s, tcg_temp_new());
if (reg0 == 7 && opsize == OS_BYTE &&
m68k_feature(s->env, M68K_FEATURE_M68000)) {
tcg_gen_subi_i32(tmp, reg, 2);
} else {
tcg_gen_subi_i32(tmp, reg, opsize_bytes(opsize));
}
return tmp;
case 5: /* Indirect displacement. */
reg = get_areg(s, reg0);
tmp = mark_to_release(s, tcg_temp_new());
ext = read_im16(env, s);
tcg_gen_addi_i32(tmp, reg, (int16_t)ext);
return tmp;
case 6: /* Indirect index + displacement. */
reg = get_areg(s, reg0);
return gen_lea_indexed(env, s, reg);
case 7: /* Other */
switch (reg0) {
case 0: /* Absolute short. */
offset = (int16_t)read_im16(env, s);
return mark_to_release(s, tcg_const_i32(offset));
case 1: /* Absolute long. */
offset = read_im32(env, s);
return mark_to_release(s, tcg_const_i32(offset));
case 2: /* pc displacement */
offset = s->pc;
offset += (int16_t)read_im16(env, s);
return mark_to_release(s, tcg_const_i32(offset));
case 3: /* pc index+displacement. */
return gen_lea_indexed(env, s, NULL_QREG);
case 4: /* Immediate. */
default:
return NULL_QREG;
}
}
/* Should never happen. */
return NULL_QREG;
}
static TCGv gen_lea(CPUM68KState *env, DisasContext *s, uint16_t insn,
int opsize)
{
int mode = extract32(insn, 3, 3);
int reg0 = REG(insn, 0);
return gen_lea_mode(env, s, mode, reg0, opsize);
}
/*
* Generate code to load/store a value from/into an EA. If WHAT > 0 this is
* a write otherwise it is a read (0 == sign extend, -1 == zero extend).
* ADDRP is non-null for readwrite operands.
*/
static TCGv gen_ea_mode(CPUM68KState *env, DisasContext *s, int mode, int reg0,
int opsize, TCGv val, TCGv *addrp, ea_what what,
int index)
{
TCGv reg, tmp, result;
int32_t offset;
switch (mode) {
case 0: /* Data register direct. */
reg = cpu_dregs[reg0];
if (what == EA_STORE) {
gen_partset_reg(opsize, reg, val);
return store_dummy;
} else {
return gen_extend(s, reg, opsize, what == EA_LOADS);
}
case 1: /* Address register direct. */
reg = get_areg(s, reg0);
if (what == EA_STORE) {
tcg_gen_mov_i32(reg, val);
return store_dummy;
} else {
return gen_extend(s, reg, opsize, what == EA_LOADS);
}
case 2: /* Indirect register */
reg = get_areg(s, reg0);
return gen_ldst(s, opsize, reg, val, what, index);
case 3: /* Indirect postincrement. */
reg = get_areg(s, reg0);
result = gen_ldst(s, opsize, reg, val, what, index);
if (what == EA_STORE || !addrp) {
TCGv tmp = tcg_temp_new();
if (reg0 == 7 && opsize == OS_BYTE &&
m68k_feature(s->env, M68K_FEATURE_M68000)) {
tcg_gen_addi_i32(tmp, reg, 2);
} else {
tcg_gen_addi_i32(tmp, reg, opsize_bytes(opsize));
}
delay_set_areg(s, reg0, tmp, true);
}
return result;
case 4: /* Indirect predecrememnt. */
if (addrp && what == EA_STORE) {
tmp = *addrp;
} else {
tmp = gen_lea_mode(env, s, mode, reg0, opsize);
if (IS_NULL_QREG(tmp)) {
return tmp;
}
if (addrp) {
*addrp = tmp;
}
}
result = gen_ldst(s, opsize, tmp, val, what, index);
if (what == EA_STORE || !addrp) {
delay_set_areg(s, reg0, tmp, false);
}
return result;
case 5: /* Indirect displacement. */
case 6: /* Indirect index + displacement. */
do_indirect:
if (addrp && what == EA_STORE) {
tmp = *addrp;
} else {
tmp = gen_lea_mode(env, s, mode, reg0, opsize);
if (IS_NULL_QREG(tmp)) {
return tmp;
}
if (addrp) {
*addrp = tmp;
}
}
return gen_ldst(s, opsize, tmp, val, what, index);
case 7: /* Other */
switch (reg0) {
case 0: /* Absolute short. */
case 1: /* Absolute long. */
case 2: /* pc displacement */
case 3: /* pc index+displacement. */
goto do_indirect;
case 4: /* Immediate. */
/* Sign extend values for consistency. */
switch (opsize) {
case OS_BYTE:
if (what == EA_LOADS) {
offset = (int8_t)read_im8(env, s);
} else {
offset = read_im8(env, s);
}
break;
case OS_WORD:
if (what == EA_LOADS) {
offset = (int16_t)read_im16(env, s);
} else {
offset = read_im16(env, s);
}
break;
case OS_LONG:
offset = read_im32(env, s);
break;
default:
g_assert_not_reached();
}
return mark_to_release(s, tcg_const_i32(offset));
default:
return NULL_QREG;
}
}
/* Should never happen. */
return NULL_QREG;
}
static TCGv gen_ea(CPUM68KState *env, DisasContext *s, uint16_t insn,
int opsize, TCGv val, TCGv *addrp, ea_what what, int index)
{
int mode = extract32(insn, 3, 3);
int reg0 = REG(insn, 0);
return gen_ea_mode(env, s, mode, reg0, opsize, val, addrp, what, index);
}
static TCGv_ptr gen_fp_ptr(int freg)
{
TCGv_ptr fp = tcg_temp_new_ptr();
tcg_gen_addi_ptr(fp, cpu_env, offsetof(CPUM68KState, fregs[freg]));
return fp;
}
static TCGv_ptr gen_fp_result_ptr(void)
{
TCGv_ptr fp = tcg_temp_new_ptr();
tcg_gen_addi_ptr(fp, cpu_env, offsetof(CPUM68KState, fp_result));
return fp;
}
static void gen_fp_move(TCGv_ptr dest, TCGv_ptr src)
{
TCGv t32;
TCGv_i64 t64;
t32 = tcg_temp_new();
tcg_gen_ld16u_i32(t32, src, offsetof(FPReg, l.upper));
tcg_gen_st16_i32(t32, dest, offsetof(FPReg, l.upper));
tcg_temp_free(t32);
t64 = tcg_temp_new_i64();
tcg_gen_ld_i64(t64, src, offsetof(FPReg, l.lower));
tcg_gen_st_i64(t64, dest, offsetof(FPReg, l.lower));
tcg_temp_free_i64(t64);
}
static void gen_load_fp(DisasContext *s, int opsize, TCGv addr, TCGv_ptr fp,
int index)
{
TCGv tmp;
TCGv_i64 t64;
t64 = tcg_temp_new_i64();
tmp = tcg_temp_new();
switch (opsize) {
case OS_BYTE:
tcg_gen_qemu_ld8s(tmp, addr, index);
gen_helper_exts32(cpu_env, fp, tmp);
break;
case OS_WORD:
tcg_gen_qemu_ld16s(tmp, addr, index);
gen_helper_exts32(cpu_env, fp, tmp);
break;
case OS_LONG:
tcg_gen_qemu_ld32u(tmp, addr, index);
gen_helper_exts32(cpu_env, fp, tmp);
break;
case OS_SINGLE:
tcg_gen_qemu_ld32u(tmp, addr, index);
gen_helper_extf32(cpu_env, fp, tmp);
break;
case OS_DOUBLE:
tcg_gen_qemu_ld64(t64, addr, index);
gen_helper_extf64(cpu_env, fp, t64);
break;
case OS_EXTENDED:
if (m68k_feature(s->env, M68K_FEATURE_CF_FPU)) {
gen_exception(s, s->base.pc_next, EXCP_FP_UNIMP);
break;
}
tcg_gen_qemu_ld32u(tmp, addr, index);
tcg_gen_shri_i32(tmp, tmp, 16);
tcg_gen_st16_i32(tmp, fp, offsetof(FPReg, l.upper));
tcg_gen_addi_i32(tmp, addr, 4);
tcg_gen_qemu_ld64(t64, tmp, index);
tcg_gen_st_i64(t64, fp, offsetof(FPReg, l.lower));
break;
case OS_PACKED:
/*
* unimplemented data type on 68040/ColdFire
* FIXME if needed for another FPU
*/
gen_exception(s, s->base.pc_next, EXCP_FP_UNIMP);
break;
default:
g_assert_not_reached();
}
tcg_temp_free(tmp);
tcg_temp_free_i64(t64);
}
static void gen_store_fp(DisasContext *s, int opsize, TCGv addr, TCGv_ptr fp,
int index)
{
TCGv tmp;
TCGv_i64 t64;
t64 = tcg_temp_new_i64();
tmp = tcg_temp_new();
switch (opsize) {
case OS_BYTE:
gen_helper_reds32(tmp, cpu_env, fp);
tcg_gen_qemu_st8(tmp, addr, index);
break;
case OS_WORD:
gen_helper_reds32(tmp, cpu_env, fp);
tcg_gen_qemu_st16(tmp, addr, index);
break;
case OS_LONG:
gen_helper_reds32(tmp, cpu_env, fp);
tcg_gen_qemu_st32(tmp, addr, index);
break;
case OS_SINGLE:
gen_helper_redf32(tmp, cpu_env, fp);
tcg_gen_qemu_st32(tmp, addr, index);
break;
case OS_DOUBLE:
gen_helper_redf64(t64, cpu_env, fp);
tcg_gen_qemu_st64(t64, addr, index);
break;
case OS_EXTENDED:
if (m68k_feature(s->env, M68K_FEATURE_CF_FPU)) {
gen_exception(s, s->base.pc_next, EXCP_FP_UNIMP);
break;
}
tcg_gen_ld16u_i32(tmp, fp, offsetof(FPReg, l.upper));
tcg_gen_shli_i32(tmp, tmp, 16);
tcg_gen_qemu_st32(tmp, addr, index);
tcg_gen_addi_i32(tmp, addr, 4);
tcg_gen_ld_i64(t64, fp, offsetof(FPReg, l.lower));
tcg_gen_qemu_st64(t64, tmp, index);
break;
case OS_PACKED:
/*
* unimplemented data type on 68040/ColdFire
* FIXME if needed for another FPU
*/
gen_exception(s, s->base.pc_next, EXCP_FP_UNIMP);
break;
default:
g_assert_not_reached();
}
tcg_temp_free(tmp);
tcg_temp_free_i64(t64);
}
static void gen_ldst_fp(DisasContext *s, int opsize, TCGv addr,
TCGv_ptr fp, ea_what what, int index)
{
if (what == EA_STORE) {
gen_store_fp(s, opsize, addr, fp, index);
} else {
gen_load_fp(s, opsize, addr, fp, index);
}
}
static int gen_ea_mode_fp(CPUM68KState *env, DisasContext *s, int mode,
int reg0, int opsize, TCGv_ptr fp, ea_what what,
int index)
{
TCGv reg, addr, tmp;
TCGv_i64 t64;
switch (mode) {
case 0: /* Data register direct. */
reg = cpu_dregs[reg0];
if (what == EA_STORE) {
switch (opsize) {
case OS_BYTE:
case OS_WORD:
case OS_LONG:
gen_helper_reds32(reg, cpu_env, fp);
break;
case OS_SINGLE:
gen_helper_redf32(reg, cpu_env, fp);
break;
default:
g_assert_not_reached();
}
} else {
tmp = tcg_temp_new();
switch (opsize) {
case OS_BYTE:
tcg_gen_ext8s_i32(tmp, reg);
gen_helper_exts32(cpu_env, fp, tmp);
break;
case OS_WORD:
tcg_gen_ext16s_i32(tmp, reg);
gen_helper_exts32(cpu_env, fp, tmp);
break;
case OS_LONG:
gen_helper_exts32(cpu_env, fp, reg);
break;
case OS_SINGLE:
gen_helper_extf32(cpu_env, fp, reg);
break;
default:
g_assert_not_reached();
}
tcg_temp_free(tmp);
}
return 0;
case 1: /* Address register direct. */
return -1;
case 2: /* Indirect register */
addr = get_areg(s, reg0);
gen_ldst_fp(s, opsize, addr, fp, what, index);
return 0;
case 3: /* Indirect postincrement. */
addr = cpu_aregs[reg0];
gen_ldst_fp(s, opsize, addr, fp, what, index);
tcg_gen_addi_i32(addr, addr, opsize_bytes(opsize));
return 0;
case 4: /* Indirect predecrememnt. */
addr = gen_lea_mode(env, s, mode, reg0, opsize);
if (IS_NULL_QREG(addr)) {
return -1;
}
gen_ldst_fp(s, opsize, addr, fp, what, index);
tcg_gen_mov_i32(cpu_aregs[reg0], addr);
return 0;
case 5: /* Indirect displacement. */
case 6: /* Indirect index + displacement. */
do_indirect:
addr = gen_lea_mode(env, s, mode, reg0, opsize);
if (IS_NULL_QREG(addr)) {
return -1;
}
gen_ldst_fp(s, opsize, addr, fp, what, index);
return 0;
case 7: /* Other */
switch (reg0) {
case 0: /* Absolute short. */
case 1: /* Absolute long. */
case 2: /* pc displacement */
case 3: /* pc index+displacement. */
goto do_indirect;
case 4: /* Immediate. */
if (what == EA_STORE) {
return -1;
}
switch (opsize) {
case OS_BYTE:
tmp = tcg_const_i32((int8_t)read_im8(env, s));
gen_helper_exts32(cpu_env, fp, tmp);
tcg_temp_free(tmp);
break;
case OS_WORD:
tmp = tcg_const_i32((int16_t)read_im16(env, s));
gen_helper_exts32(cpu_env, fp, tmp);
tcg_temp_free(tmp);
break;
case OS_LONG:
tmp = tcg_const_i32(read_im32(env, s));
gen_helper_exts32(cpu_env, fp, tmp);
tcg_temp_free(tmp);
break;
case OS_SINGLE:
tmp = tcg_const_i32(read_im32(env, s));
gen_helper_extf32(cpu_env, fp, tmp);
tcg_temp_free(tmp);
break;
case OS_DOUBLE:
t64 = tcg_const_i64(read_im64(env, s));
gen_helper_extf64(cpu_env, fp, t64);
tcg_temp_free_i64(t64);
break;
case OS_EXTENDED:
if (m68k_feature(s->env, M68K_FEATURE_CF_FPU)) {
gen_exception(s, s->base.pc_next, EXCP_FP_UNIMP);
break;
}
tmp = tcg_const_i32(read_im32(env, s) >> 16);
tcg_gen_st16_i32(tmp, fp, offsetof(FPReg, l.upper));
tcg_temp_free(tmp);
t64 = tcg_const_i64(read_im64(env, s));
tcg_gen_st_i64(t64, fp, offsetof(FPReg, l.lower));
tcg_temp_free_i64(t64);
break;
case OS_PACKED:
/*
* unimplemented data type on 68040/ColdFire
* FIXME if needed for another FPU
*/
gen_exception(s, s->base.pc_next, EXCP_FP_UNIMP);
break;
default:
g_assert_not_reached();
}
return 0;
default:
return -1;
}
}
return -1;
}
static int gen_ea_fp(CPUM68KState *env, DisasContext *s, uint16_t insn,
int opsize, TCGv_ptr fp, ea_what what, int index)
{
int mode = extract32(insn, 3, 3);
int reg0 = REG(insn, 0);
return gen_ea_mode_fp(env, s, mode, reg0, opsize, fp, what, index);
}
typedef struct {
TCGCond tcond;
bool g1;
bool g2;
TCGv v1;
TCGv v2;
} DisasCompare;
static void gen_cc_cond(DisasCompare *c, DisasContext *s, int cond)
{
TCGv tmp, tmp2;
TCGCond tcond;
CCOp op = s->cc_op;
/* The CC_OP_CMP form can handle most normal comparisons directly. */
if (op == CC_OP_CMPB || op == CC_OP_CMPW || op == CC_OP_CMPL) {
c->g1 = c->g2 = 1;
c->v1 = QREG_CC_N;
c->v2 = QREG_CC_V;
switch (cond) {
case 2: /* HI */
case 3: /* LS */
tcond = TCG_COND_LEU;
goto done;
case 4: /* CC */
case 5: /* CS */
tcond = TCG_COND_LTU;
goto done;
case 6: /* NE */
case 7: /* EQ */
tcond = TCG_COND_EQ;
goto done;
case 10: /* PL */
case 11: /* MI */
c->g1 = c->g2 = 0;
c->v2 = tcg_const_i32(0);
c->v1 = tmp = tcg_temp_new();
tcg_gen_sub_i32(tmp, QREG_CC_N, QREG_CC_V);
gen_ext(tmp, tmp, op - CC_OP_CMPB, 1);
/* fallthru */
case 12: /* GE */
case 13: /* LT */
tcond = TCG_COND_LT;
goto done;
case 14: /* GT */
case 15: /* LE */
tcond = TCG_COND_LE;
goto done;
}
}
c->g1 = 1;
c->g2 = 0;
c->v2 = tcg_const_i32(0);
switch (cond) {
case 0: /* T */
case 1: /* F */
c->v1 = c->v2;
tcond = TCG_COND_NEVER;
goto done;
case 14: /* GT (!(Z || (N ^ V))) */
case 15: /* LE (Z || (N ^ V)) */
/*
* Logic operations clear V, which simplifies LE to (Z || N),
* and since Z and N are co-located, this becomes a normal
* comparison vs N.
*/
if (op == CC_OP_LOGIC) {
c->v1 = QREG_CC_N;
tcond = TCG_COND_LE;
goto done;
}
break;
case 12: /* GE (!(N ^ V)) */
case 13: /* LT (N ^ V) */
/* Logic operations clear V, which simplifies this to N. */
if (op != CC_OP_LOGIC) {
break;
}
/* fallthru */
case 10: /* PL (!N) */
case 11: /* MI (N) */
/* Several cases represent N normally. */
if (op == CC_OP_ADDB || op == CC_OP_ADDW || op == CC_OP_ADDL ||
op == CC_OP_SUBB || op == CC_OP_SUBW || op == CC_OP_SUBL ||
op == CC_OP_LOGIC) {
c->v1 = QREG_CC_N;
tcond = TCG_COND_LT;
goto done;
}
break;
case 6: /* NE (!Z) */
case 7: /* EQ (Z) */
/* Some cases fold Z into N. */
if (op == CC_OP_ADDB || op == CC_OP_ADDW || op == CC_OP_ADDL ||
op == CC_OP_SUBB || op == CC_OP_SUBW || op == CC_OP_SUBL ||
op == CC_OP_LOGIC) {
tcond = TCG_COND_EQ;
c->v1 = QREG_CC_N;
goto done;
}
break;
case 4: /* CC (!C) */
case 5: /* CS (C) */
/* Some cases fold C into X. */
if (op == CC_OP_ADDB || op == CC_OP_ADDW || op == CC_OP_ADDL ||
op == CC_OP_SUBB || op == CC_OP_SUBW || op == CC_OP_SUBL) {
tcond = TCG_COND_NE;
c->v1 = QREG_CC_X;
goto done;
}
/* fallthru */
case 8: /* VC (!V) */
case 9: /* VS (V) */
/* Logic operations clear V and C. */
if (op == CC_OP_LOGIC) {
tcond = TCG_COND_NEVER;
c->v1 = c->v2;
goto done;
}
break;
}
/* Otherwise, flush flag state to CC_OP_FLAGS. */
gen_flush_flags(s);
switch (cond) {
case 0: /* T */
case 1: /* F */
default:
/* Invalid, or handled above. */
abort();
case 2: /* HI (!C && !Z) -> !(C || Z)*/
case 3: /* LS (C || Z) */
c->v1 = tmp = tcg_temp_new();
c->g1 = 0;
tcg_gen_setcond_i32(TCG_COND_EQ, tmp, QREG_CC_Z, c->v2);
tcg_gen_or_i32(tmp, tmp, QREG_CC_C);
tcond = TCG_COND_NE;
break;
case 4: /* CC (!C) */
case 5: /* CS (C) */
c->v1 = QREG_CC_C;
tcond = TCG_COND_NE;
break;
case 6: /* NE (!Z) */
case 7: /* EQ (Z) */
c->v1 = QREG_CC_Z;
tcond = TCG_COND_EQ;
break;
case 8: /* VC (!V) */
case 9: /* VS (V) */
c->v1 = QREG_CC_V;
tcond = TCG_COND_LT;
break;
case 10: /* PL (!N) */
case 11: /* MI (N) */
c->v1 = QREG_CC_N;
tcond = TCG_COND_LT;
break;
case 12: /* GE (!(N ^ V)) */
case 13: /* LT (N ^ V) */
c->v1 = tmp = tcg_temp_new();
c->g1 = 0;
tcg_gen_xor_i32(tmp, QREG_CC_N, QREG_CC_V);
tcond = TCG_COND_LT;
break;
case 14: /* GT (!(Z || (N ^ V))) */
case 15: /* LE (Z || (N ^ V)) */
c->v1 = tmp = tcg_temp_new();
c->g1 = 0;
tcg_gen_setcond_i32(TCG_COND_EQ, tmp, QREG_CC_Z, c->v2);
tcg_gen_neg_i32(tmp, tmp);
tmp2 = tcg_temp_new();
tcg_gen_xor_i32(tmp2, QREG_CC_N, QREG_CC_V);
tcg_gen_or_i32(tmp, tmp, tmp2);
tcg_temp_free(tmp2);
tcond = TCG_COND_LT;
break;
}
done:
if ((cond & 1) == 0) {
tcond = tcg_invert_cond(tcond);
}
c->tcond = tcond;
}
static void free_cond(DisasCompare *c)
{
if (!c->g1) {
tcg_temp_free(c->v1);
}
if (!c->g2) {
tcg_temp_free(c->v2);
}
}
static void gen_jmpcc(DisasContext *s, int cond, TCGLabel *l1)
{
DisasCompare c;
gen_cc_cond(&c, s, cond);
update_cc_op(s);
tcg_gen_brcond_i32(c.tcond, c.v1, c.v2, l1);
free_cond(&c);
}
/* Force a TB lookup after an instruction that changes the CPU state. */
static void gen_exit_tb(DisasContext *s)
{
update_cc_op(s);
tcg_gen_movi_i32(QREG_PC, s->pc);
s->base.is_jmp = DISAS_EXIT;
}
#define SRC_EA(env, result, opsize, op_sign, addrp) do { \
result = gen_ea(env, s, insn, opsize, NULL_QREG, addrp, \
op_sign ? EA_LOADS : EA_LOADU, IS_USER(s)); \
if (IS_NULL_QREG(result)) { \
gen_addr_fault(s); \
return; \
} \
} while (0)
#define DEST_EA(env, insn, opsize, val, addrp) do { \
TCGv ea_result = gen_ea(env, s, insn, opsize, val, addrp, \
EA_STORE, IS_USER(s)); \
if (IS_NULL_QREG(ea_result)) { \
gen_addr_fault(s); \
return; \
} \
} while (0)
static inline bool use_goto_tb(DisasContext *s, uint32_t dest)
{
#ifndef CONFIG_USER_ONLY
return (s->base.pc_first & TARGET_PAGE_MASK) == (dest & TARGET_PAGE_MASK)
|| (s->base.pc_next & TARGET_PAGE_MASK) == (dest & TARGET_PAGE_MASK);
#else
return true;
#endif
}
/* Generate a jump to an immediate address. */
static void gen_jmp_tb(DisasContext *s, int n, uint32_t dest)
{
if (unlikely(s->base.singlestep_enabled)) {
gen_exception(s, dest, EXCP_DEBUG);
} else if (use_goto_tb(s, dest)) {
tcg_gen_goto_tb(n);
tcg_gen_movi_i32(QREG_PC, dest);
tcg_gen_exit_tb(s->base.tb, n);
} else {
gen_jmp_im(s, dest);
tcg_gen_exit_tb(NULL, 0);
}
s->base.is_jmp = DISAS_NORETURN;
}
DISAS_INSN(scc)
{
DisasCompare c;
int cond;
TCGv tmp;
cond = (insn >> 8) & 0xf;
gen_cc_cond(&c, s, cond);
tmp = tcg_temp_new();
tcg_gen_setcond_i32(c.tcond, tmp, c.v1, c.v2);
free_cond(&c);
tcg_gen_neg_i32(tmp, tmp);
DEST_EA(env, insn, OS_BYTE, tmp, NULL);
tcg_temp_free(tmp);
}
DISAS_INSN(dbcc)
{
TCGLabel *l1;
TCGv reg;
TCGv tmp;
int16_t offset;
uint32_t base;
reg = DREG(insn, 0);
base = s->pc;
offset = (int16_t)read_im16(env, s);
l1 = gen_new_label();
gen_jmpcc(s, (insn >> 8) & 0xf, l1);
tmp = tcg_temp_new();
tcg_gen_ext16s_i32(tmp, reg);
tcg_gen_addi_i32(tmp, tmp, -1);
gen_partset_reg(OS_WORD, reg, tmp);
tcg_gen_brcondi_i32(TCG_COND_EQ, tmp, -1, l1);
gen_jmp_tb(s, 1, base + offset);
gen_set_label(l1);
gen_jmp_tb(s, 0, s->pc);
}
DISAS_INSN(undef_mac)
{
gen_exception(s, s->base.pc_next, EXCP_LINEA);
}
DISAS_INSN(undef_fpu)
{
gen_exception(s, s->base.pc_next, EXCP_LINEF);
}
DISAS_INSN(undef)
{
/*
* ??? This is both instructions that are as yet unimplemented
* for the 680x0 series, as well as those that are implemented
* but actually illegal for CPU32 or pre-68020.
*/
qemu_log_mask(LOG_UNIMP, "Illegal instruction: %04x @ %08x\n",
insn, s->base.pc_next);
gen_exception(s, s->base.pc_next, EXCP_ILLEGAL);
}
DISAS_INSN(mulw)
{
TCGv reg;
TCGv tmp;
TCGv src;
int sign;
sign = (insn & 0x100) != 0;
reg = DREG(insn, 9);
tmp = tcg_temp_new();
if (sign)
tcg_gen_ext16s_i32(tmp, reg);
else
tcg_gen_ext16u_i32(tmp, reg);
SRC_EA(env, src, OS_WORD, sign, NULL);
tcg_gen_mul_i32(tmp, tmp, src);
tcg_gen_mov_i32(reg, tmp);
gen_logic_cc(s, tmp, OS_LONG);
tcg_temp_free(tmp);
}
DISAS_INSN(divw)
{
int sign;
TCGv src;
TCGv destr;
/* divX.w <EA>,Dn 32/16 -> 16r:16q */
sign = (insn & 0x100) != 0;
/* dest.l / src.w */
SRC_EA(env, src, OS_WORD, sign, NULL);
destr = tcg_const_i32(REG(insn, 9));
if (sign) {
gen_helper_divsw(cpu_env, destr, src);
} else {
gen_helper_divuw(cpu_env, destr, src);
}
tcg_temp_free(destr);
set_cc_op(s, CC_OP_FLAGS);
}
DISAS_INSN(divl)
{
TCGv num, reg, den;
int sign;
uint16_t ext;
ext = read_im16(env, s);
sign = (ext & 0x0800) != 0;
if (ext & 0x400) {
if (!m68k_feature(s->env, M68K_FEATURE_QUAD_MULDIV)) {
gen_exception(s, s->base.pc_next, EXCP_ILLEGAL);
return;
}
/* divX.l <EA>, Dr:Dq 64/32 -> 32r:32q */
SRC_EA(env, den, OS_LONG, 0, NULL);
num = tcg_const_i32(REG(ext, 12));
reg = tcg_const_i32(REG(ext, 0));
if (sign) {
gen_helper_divsll(cpu_env, num, reg, den);
} else {
gen_helper_divull(cpu_env, num, reg, den);
}
tcg_temp_free(reg);
tcg_temp_free(num);
set_cc_op(s, CC_OP_FLAGS);
return;
}
/* divX.l <EA>, Dq 32/32 -> 32q */
/* divXl.l <EA>, Dr:Dq 32/32 -> 32r:32q */
SRC_EA(env, den, OS_LONG, 0, NULL);
num = tcg_const_i32(REG(ext, 12));
reg = tcg_const_i32(REG(ext, 0));
if (sign) {
gen_helper_divsl(cpu_env, num, reg, den);
} else {
gen_helper_divul(cpu_env, num, reg, den);
}
tcg_temp_free(reg);
tcg_temp_free(num);
set_cc_op(s, CC_OP_FLAGS);
}
static void bcd_add(TCGv dest, TCGv src)
{
TCGv t0, t1;
/*
* dest10 = dest10 + src10 + X
*
* t1 = src
* t2 = t1 + 0x066
* t3 = t2 + dest + X
* t4 = t2 ^ dest
* t5 = t3 ^ t4
* t6 = ~t5 & 0x110
* t7 = (t6 >> 2) | (t6 >> 3)
* return t3 - t7
*/
/*
* t1 = (src + 0x066) + dest + X
* = result with some possible exceding 0x6
*/
t0 = tcg_const_i32(0x066);
tcg_gen_add_i32(t0, t0, src);
t1 = tcg_temp_new();
tcg_gen_add_i32(t1, t0, dest);
tcg_gen_add_i32(t1, t1, QREG_CC_X);
/* we will remove exceding 0x6 where there is no carry */
/*
* t0 = (src + 0x0066) ^ dest
* = t1 without carries
*/
tcg_gen_xor_i32(t0, t0, dest);
/*
* extract the carries
* t0 = t0 ^ t1
* = only the carries
*/
tcg_gen_xor_i32(t0, t0, t1);
/*
* generate 0x1 where there is no carry
* and for each 0x10, generate a 0x6
*/
tcg_gen_shri_i32(t0, t0, 3);
tcg_gen_not_i32(t0, t0);
tcg_gen_andi_i32(t0, t0, 0x22);
tcg_gen_add_i32(dest, t0, t0);
tcg_gen_add_i32(dest, dest, t0);
tcg_temp_free(t0);
/*
* remove the exceding 0x6
* for digits that have not generated a carry
*/
tcg_gen_sub_i32(dest, t1, dest);
tcg_temp_free(t1);
}
static void bcd_sub(TCGv dest, TCGv src)
{
TCGv t0, t1, t2;
/*
* dest10 = dest10 - src10 - X
* = bcd_add(dest + 1 - X, 0x199 - src)
*/
/* t0 = 0x066 + (0x199 - src) */
t0 = tcg_temp_new();
tcg_gen_subfi_i32(t0, 0x1ff, src);
/* t1 = t0 + dest + 1 - X*/
t1 = tcg_temp_new();
tcg_gen_add_i32(t1, t0, dest);
tcg_gen_addi_i32(t1, t1, 1);
tcg_gen_sub_i32(t1, t1, QREG_CC_X);
/* t2 = t0 ^ dest */
t2 = tcg_temp_new();
tcg_gen_xor_i32(t2, t0, dest);
/* t0 = t1 ^ t2 */
tcg_gen_xor_i32(t0, t1, t2);
/*
* t2 = ~t0 & 0x110
* t0 = (t2 >> 2) | (t2 >> 3)
*
* to fit on 8bit operands, changed in:
*
* t2 = ~(t0 >> 3) & 0x22
* t0 = t2 + t2
* t0 = t0 + t2
*/
tcg_gen_shri_i32(t2, t0, 3);
tcg_gen_not_i32(t2, t2);
tcg_gen_andi_i32(t2, t2, 0x22);
tcg_gen_add_i32(t0, t2, t2);
tcg_gen_add_i32(t0, t0, t2);
tcg_temp_free(t2);
/* return t1 - t0 */
tcg_gen_sub_i32(dest, t1, t0);
tcg_temp_free(t0);
tcg_temp_free(t1);
}
static void bcd_flags(TCGv val)
{
tcg_gen_andi_i32(QREG_CC_C, val, 0x0ff);
tcg_gen_or_i32(QREG_CC_Z, QREG_CC_Z, QREG_CC_C);
tcg_gen_extract_i32(QREG_CC_C, val, 8, 1);
tcg_gen_mov_i32(QREG_CC_X, QREG_CC_C);
}
DISAS_INSN(abcd_reg)
{
TCGv src;
TCGv dest;
gen_flush_flags(s); /* !Z is sticky */
src = gen_extend(s, DREG(insn, 0), OS_BYTE, 0);
dest = gen_extend(s, DREG(insn, 9), OS_BYTE, 0);
bcd_add(dest, src);
gen_partset_reg(OS_BYTE, DREG(insn, 9), dest);
bcd_flags(dest);
}
DISAS_INSN(abcd_mem)
{
TCGv src, dest, addr;
gen_flush_flags(s); /* !Z is sticky */
/* Indirect pre-decrement load (mode 4) */
src = gen_ea_mode(env, s, 4, REG(insn, 0), OS_BYTE,
NULL_QREG, NULL, EA_LOADU, IS_USER(s));
dest = gen_ea_mode(env, s, 4, REG(insn, 9), OS_BYTE,
NULL_QREG, &addr, EA_LOADU, IS_USER(s));
bcd_add(dest, src);
gen_ea_mode(env, s, 4, REG(insn, 9), OS_BYTE, dest, &addr,
EA_STORE, IS_USER(s));
bcd_flags(dest);
}
DISAS_INSN(sbcd_reg)
{
TCGv src, dest;
gen_flush_flags(s); /* !Z is sticky */
src = gen_extend(s, DREG(insn, 0), OS_BYTE, 0);
dest = gen_extend(s, DREG(insn, 9), OS_BYTE, 0);
bcd_sub(dest, src);
gen_partset_reg(OS_BYTE, DREG(insn, 9), dest);
bcd_flags(dest);
}
DISAS_INSN(sbcd_mem)
{
TCGv src, dest, addr;
gen_flush_flags(s); /* !Z is sticky */
/* Indirect pre-decrement load (mode 4) */
src = gen_ea_mode(env, s, 4, REG(insn, 0), OS_BYTE,
NULL_QREG, NULL, EA_LOADU, IS_USER(s));
dest = gen_ea_mode(env, s, 4, REG(insn, 9), OS_BYTE,
NULL_QREG, &addr, EA_LOADU, IS_USER(s));
bcd_sub(dest, src);
gen_ea_mode(env, s, 4, REG(insn, 9), OS_BYTE, dest, &addr,
EA_STORE, IS_USER(s));
bcd_flags(dest);
}
DISAS_INSN(nbcd)
{
TCGv src, dest;
TCGv addr;
gen_flush_flags(s); /* !Z is sticky */
SRC_EA(env, src, OS_BYTE, 0, &addr);
dest = tcg_const_i32(0);
bcd_sub(dest, src);
DEST_EA(env, insn, OS_BYTE, dest, &addr);
bcd_flags(dest);
tcg_temp_free(dest);
}
DISAS_INSN(addsub)
{
TCGv reg;
TCGv dest;
TCGv src;
TCGv tmp;
TCGv addr;
int add;
int opsize;
add = (insn & 0x4000) != 0;
opsize = insn_opsize(insn);
reg = gen_extend(s, DREG(insn, 9), opsize, 1);
dest = tcg_temp_new();
if (insn & 0x100) {
SRC_EA(env, tmp, opsize, 1, &addr);
src = reg;
} else {
tmp = reg;
SRC_EA(env, src, opsize, 1, NULL);
}
if (add) {
tcg_gen_add_i32(dest, tmp, src);
tcg_gen_setcond_i32(TCG_COND_LTU, QREG_CC_X, dest, src);
set_cc_op(s, CC_OP_ADDB + opsize);
} else {
tcg_gen_setcond_i32(TCG_COND_LTU, QREG_CC_X, tmp, src);
tcg_gen_sub_i32(dest, tmp, src);
set_cc_op(s, CC_OP_SUBB + opsize);
}
gen_update_cc_add(dest, src, opsize);
if (insn & 0x100) {
DEST_EA(env, insn, opsize, dest, &addr);
} else {
gen_partset_reg(opsize, DREG(insn, 9), dest);
}
tcg_temp_free(dest);
}
/* Reverse the order of the bits in REG. */
DISAS_INSN(bitrev)
{
TCGv reg;
reg = DREG(insn, 0);
gen_helper_bitrev(reg, reg);
}
DISAS_INSN(bitop_reg)
{
int opsize;
int op;
TCGv src1;
TCGv src2;
TCGv tmp;
TCGv addr;
TCGv dest;
if ((insn & 0x38) != 0)
opsize = OS_BYTE;
else
opsize = OS_LONG;
op = (insn >> 6) & 3;
SRC_EA(env, src1, opsize, 0, op ? &addr: NULL);
gen_flush_flags(s);
src2 = tcg_temp_new();
if (opsize == OS_BYTE)
tcg_gen_andi_i32(src2, DREG(insn, 9), 7);
else
tcg_gen_andi_i32(src2, DREG(insn, 9), 31);
tmp = tcg_const_i32(1);
tcg_gen_shl_i32(tmp, tmp, src2);
tcg_temp_free(src2);
tcg_gen_and_i32(QREG_CC_Z, src1, tmp);
dest = tcg_temp_new();
switch (op) {
case 1: /* bchg */
tcg_gen_xor_i32(dest, src1, tmp);
break;
case 2: /* bclr */
tcg_gen_andc_i32(dest, src1, tmp);
break;
case 3: /* bset */
tcg_gen_or_i32(dest, src1, tmp);
break;
default: /* btst */
break;
}
tcg_temp_free(tmp);
if (op) {
DEST_EA(env, insn, opsize, dest, &addr);
}
tcg_temp_free(dest);
}
DISAS_INSN(sats)
{
TCGv reg;
reg = DREG(insn, 0);
gen_flush_flags(s);
gen_helper_sats(reg, reg, QREG_CC_V);
gen_logic_cc(s, reg, OS_LONG);
}
static void gen_push(DisasContext *s, TCGv val)
{
TCGv tmp;
tmp = tcg_temp_new();
tcg_gen_subi_i32(tmp, QREG_SP, 4);
gen_store(s, OS_LONG, tmp, val, IS_USER(s));
tcg_gen_mov_i32(QREG_SP, tmp);
tcg_temp_free(tmp);
}
static TCGv mreg(int reg)
{
if (reg < 8) {
/* Dx */
return cpu_dregs[reg];
}
/* Ax */
return cpu_aregs[reg & 7];
}
DISAS_INSN(movem)
{
TCGv addr, incr, tmp, r[16];
int is_load = (insn & 0x0400) != 0;
int opsize = (insn & 0x40) != 0 ? OS_LONG : OS_WORD;
uint16_t mask = read_im16(env, s);
int mode = extract32(insn, 3, 3);
int reg0 = REG(insn, 0);
int i;
tmp = cpu_aregs[reg0];
switch (mode) {
case 0: /* data register direct */
case 1: /* addr register direct */
do_addr_fault:
gen_addr_fault(s);
return;
case 2: /* indirect */
break;
case 3: /* indirect post-increment */
if (!is_load) {
/* post-increment is not allowed */
goto do_addr_fault;
}
break;
case 4: /* indirect pre-decrement */
if (is_load) {
/* pre-decrement is not allowed */
goto do_addr_fault;
}
/*
* We want a bare copy of the address reg, without any pre-decrement
* adjustment, as gen_lea would provide.
*/
break;
default:
tmp = gen_lea_mode(env, s, mode, reg0, opsize);
if (IS_NULL_QREG(tmp)) {
goto do_addr_fault;
}
break;
}
addr = tcg_temp_new();
tcg_gen_mov_i32(addr, tmp);
incr = tcg_const_i32(opsize_bytes(opsize));
if (is_load) {
/* memory to register */
for (i = 0; i < 16; i++) {
if (mask & (1 << i)) {
r[i] = gen_load(s, opsize, addr, 1, IS_USER(s));
tcg_gen_add_i32(addr, addr, incr);
}
}
for (i = 0; i < 16; i++) {
if (mask & (1 << i)) {
tcg_gen_mov_i32(mreg(i), r[i]);
tcg_temp_free(r[i]);
}
}
if (mode == 3) {
/* post-increment: movem (An)+,X */
tcg_gen_mov_i32(cpu_aregs[reg0], addr);
}
} else {
/* register to memory */
if (mode == 4) {
/* pre-decrement: movem X,-(An) */
for (i = 15; i >= 0; i--) {
if ((mask << i) & 0x8000) {
tcg_gen_sub_i32(addr, addr, incr);
if (reg0 + 8 == i &&
m68k_feature(s->env, M68K_FEATURE_EXT_FULL)) {
/*
* M68020+: if the addressing register is the
* register moved to memory, the value written
* is the initial value decremented by the size of
* the operation, regardless of how many actual
* stores have been performed until this point.
* M68000/M68010: the value is the initial value.
*/
tmp = tcg_temp_new();
tcg_gen_sub_i32(tmp, cpu_aregs[reg0], incr);
gen_store(s, opsize, addr, tmp, IS_USER(s));
tcg_temp_free(tmp);
} else {
gen_store(s, opsize, addr, mreg(i), IS_USER(s));
}
}
}
tcg_gen_mov_i32(cpu_aregs[reg0], addr);
} else {
for (i = 0; i < 16; i++) {
if (mask & (1 << i)) {
gen_store(s, opsize, addr, mreg(i), IS_USER(s));
tcg_gen_add_i32(addr, addr, incr);
}
}
}
}
tcg_temp_free(incr);
tcg_temp_free(addr);
}
DISAS_INSN(movep)
{
uint8_t i;
int16_t displ;
TCGv reg;
TCGv addr;
TCGv abuf;
TCGv dbuf;
displ = read_im16(env, s);
addr = AREG(insn, 0);
reg = DREG(insn, 9);
abuf = tcg_temp_new();
tcg_gen_addi_i32(abuf, addr, displ);
dbuf = tcg_temp_new();
if (insn & 0x40) {
i = 4;
} else {
i = 2;
}
if (insn & 0x80) {
for ( ; i > 0 ; i--) {
tcg_gen_shri_i32(dbuf, reg, (i - 1) * 8);
tcg_gen_qemu_st8(dbuf, abuf, IS_USER(s));
if (i > 1) {
tcg_gen_addi_i32(abuf, abuf, 2);
}
}
} else {
for ( ; i > 0 ; i--) {
tcg_gen_qemu_ld8u(dbuf, abuf, IS_USER(s));
tcg_gen_deposit_i32(reg, reg, dbuf, (i - 1) * 8, 8);
if (i > 1) {
tcg_gen_addi_i32(abuf, abuf, 2);
}
}
}
tcg_temp_free(abuf);
tcg_temp_free(dbuf);
}
DISAS_INSN(bitop_im)
{
int opsize;
int op;
TCGv src1;
uint32_t mask;
int bitnum;
TCGv tmp;
TCGv addr;
if ((insn & 0x38) != 0)
opsize = OS_BYTE;
else
opsize = OS_LONG;
op = (insn >> 6) & 3;
bitnum = read_im16(env, s);
if (m68k_feature(s->env, M68K_FEATURE_M68000)) {
if (bitnum & 0xfe00) {
disas_undef(env, s, insn);
return;
}
} else {
if (bitnum & 0xff00) {
disas_undef(env, s, insn);
return;
}
}
SRC_EA(env, src1, opsize, 0, op ? &addr: NULL);
gen_flush_flags(s);
if (opsize == OS_BYTE)
bitnum &= 7;
else
bitnum &= 31;
mask = 1 << bitnum;
tcg_gen_andi_i32(QREG_CC_Z, src1, mask);
if (op) {
tmp = tcg_temp_new();
switch (op) {
case 1: /* bchg */
tcg_gen_xori_i32(tmp, src1, mask);
break;
case 2: /* bclr */
tcg_gen_andi_i32(tmp, src1, ~mask);
break;
case 3: /* bset */
tcg_gen_ori_i32(tmp, src1, mask);
break;
default: /* btst */
break;
}
DEST_EA(env, insn, opsize, tmp, &addr);
tcg_temp_free(tmp);
}
}
static TCGv gen_get_ccr(DisasContext *s)
{
TCGv dest;
update_cc_op(s);
dest = tcg_temp_new();
gen_helper_get_ccr(dest, cpu_env);
return dest;
}
static TCGv gen_get_sr(DisasContext *s)
{
TCGv ccr;
TCGv sr;
ccr = gen_get_ccr(s);
sr = tcg_temp_new();
tcg_gen_andi_i32(sr, QREG_SR, 0xffe0);
tcg_gen_or_i32(sr, sr, ccr);
tcg_temp_free(ccr);
return sr;
}
static void gen_set_sr_im(DisasContext *s, uint16_t val, int ccr_only)
{
if (ccr_only) {
tcg_gen_movi_i32(QREG_CC_C, val & CCF_C ? 1 : 0);
tcg_gen_movi_i32(QREG_CC_V, val & CCF_V ? -1 : 0);
tcg_gen_movi_i32(QREG_CC_Z, val & CCF_Z ? 0 : 1);
tcg_gen_movi_i32(QREG_CC_N, val & CCF_N ? -1 : 0);
tcg_gen_movi_i32(QREG_CC_X, val & CCF_X ? 1 : 0);
} else {
TCGv sr = tcg_const_i32(val);
gen_helper_set_sr(cpu_env, sr);
tcg_temp_free(sr);
}
set_cc_op(s, CC_OP_FLAGS);
}
static void gen_set_sr(DisasContext *s, TCGv val, int ccr_only)
{
if (ccr_only) {
gen_helper_set_ccr(cpu_env, val);
} else {
gen_helper_set_sr(cpu_env, val);
}
set_cc_op(s, CC_OP_FLAGS);
}
static void gen_move_to_sr(CPUM68KState *env, DisasContext *s, uint16_t insn,
bool ccr_only)
{
if ((insn & 0x3f) == 0x3c) {
uint16_t val;
val = read_im16(env, s);
gen_set_sr_im(s, val, ccr_only);
} else {
TCGv src;
SRC_EA(env, src, OS_WORD, 0, NULL);
gen_set_sr(s, src, ccr_only);
}
}
DISAS_INSN(arith_im)
{
int op;
TCGv im;
TCGv src1;
TCGv dest;
TCGv addr;
int opsize;
bool with_SR = ((insn & 0x3f) == 0x3c);
op = (insn >> 9) & 7;
opsize = insn_opsize(insn);
switch (opsize) {
case OS_BYTE:
im = tcg_const_i32((int8_t)read_im8(env, s));
break;
case OS_WORD:
im = tcg_const_i32((int16_t)read_im16(env, s));
break;
case OS_LONG:
im = tcg_const_i32(read_im32(env, s));
break;
default:
g_assert_not_reached();
}
if (with_SR) {
/* SR/CCR can only be used with andi/eori/ori */
if (op == 2 || op == 3 || op == 6) {
disas_undef(env, s, insn);
return;
}
switch (opsize) {
case OS_BYTE:
src1 = gen_get_ccr(s);
break;
case OS_WORD:
if (IS_USER(s)) {
gen_exception(s, s->base.pc_next, EXCP_PRIVILEGE);
return;
}
src1 = gen_get_sr(s);
break;
default:
/* OS_LONG; others already g_assert_not_reached. */
disas_undef(env, s, insn);
return;
}
} else {
SRC_EA(env, src1, opsize, 1, (op == 6) ? NULL : &addr);
}
dest = tcg_temp_new();
switch (op) {
case 0: /* ori */
tcg_gen_or_i32(dest, src1, im);
if (with_SR) {
gen_set_sr(s, dest, opsize == OS_BYTE);
} else {
DEST_EA(env, insn, opsize, dest, &addr);
gen_logic_cc(s, dest, opsize);
}
break;
case 1: /* andi */
tcg_gen_and_i32(dest, src1, im);
if (with_SR) {
gen_set_sr(s, dest, opsize == OS_BYTE);
} else {
DEST_EA(env, insn, opsize, dest, &addr);
gen_logic_cc(s, dest, opsize);
}
break;
case 2: /* subi */
tcg_gen_setcond_i32(TCG_COND_LTU, QREG_CC_X, src1, im);
tcg_gen_sub_i32(dest, src1, im);
gen_update_cc_add(dest, im, opsize);
set_cc_op(s, CC_OP_SUBB + opsize);
DEST_EA(env, insn, opsize, dest, &addr);
break;
case 3: /* addi */
tcg_gen_add_i32(dest, src1, im);
gen_update_cc_add(dest, im, opsize);
tcg_gen_setcond_i32(TCG_COND_LTU, QREG_CC_X, dest, im);
set_cc_op(s, CC_OP_ADDB + opsize);
DEST_EA(env, insn, opsize, dest, &addr);
break;
case 5: /* eori */
tcg_gen_xor_i32(dest, src1, im);
if (with_SR) {
gen_set_sr(s, dest, opsize == OS_BYTE);
} else {
DEST_EA(env, insn, opsize, dest, &addr);
gen_logic_cc(s, dest, opsize);
}
break;
case 6: /* cmpi */
gen_update_cc_cmp(s, src1, im, opsize);
break;
default:
abort();
}
tcg_temp_free(im);
tcg_temp_free(dest);
}
DISAS_INSN(cas)
{
int opsize;
TCGv addr;
uint16_t ext;
TCGv load;
TCGv cmp;
MemOp opc;
switch ((insn >> 9) & 3) {
case 1:
opsize = OS_BYTE;
opc = MO_SB;
break;
case 2:
opsize = OS_WORD;
opc = MO_TESW;
break;
case 3:
opsize = OS_LONG;
opc = MO_TESL;
break;
default:
g_assert_not_reached();
}
ext = read_im16(env, s);
/* cas Dc,Du,<EA> */
addr = gen_lea(env, s, insn, opsize);
if (IS_NULL_QREG(addr)) {
gen_addr_fault(s);
return;
}
cmp = gen_extend(s, DREG(ext, 0), opsize, 1);
/*
* if <EA> == Dc then
* <EA> = Du
* Dc = <EA> (because <EA> == Dc)
* else
* Dc = <EA>
*/
load = tcg_temp_new();
tcg_gen_atomic_cmpxchg_i32(load, addr, cmp, DREG(ext, 6),
IS_USER(s), opc);
/* update flags before setting cmp to load */
gen_update_cc_cmp(s, load, cmp, opsize);
gen_partset_reg(opsize, DREG(ext, 0), load);
tcg_temp_free(load);
switch (extract32(insn, 3, 3)) {
case 3: /* Indirect postincrement. */
tcg_gen_addi_i32(AREG(insn, 0), addr, opsize_bytes(opsize));
break;
case 4: /* Indirect predecrememnt. */
tcg_gen_mov_i32(AREG(insn, 0), addr);
break;
}
}
DISAS_INSN(cas2w)
{
uint16_t ext1, ext2;
TCGv addr1, addr2;
TCGv regs;
/* cas2 Dc1:Dc2,Du1:Du2,(Rn1):(Rn2) */
ext1 = read_im16(env, s);
if (ext1 & 0x8000) {
/* Address Register */
addr1 = AREG(ext1, 12);
} else {
/* Data Register */
addr1 = DREG(ext1, 12);
}
ext2 = read_im16(env, s);
if (ext2 & 0x8000) {
/* Address Register */
addr2 = AREG(ext2, 12);
} else {
/* Data Register */
addr2 = DREG(ext2, 12);
}
/*
* if (R1) == Dc1 && (R2) == Dc2 then
* (R1) = Du1
* (R2) = Du2
* else
* Dc1 = (R1)
* Dc2 = (R2)
*/
regs = tcg_const_i32(REG(ext2, 6) |
(REG(ext1, 6) << 3) |
(REG(ext2, 0) << 6) |
(REG(ext1, 0) << 9));
if (tb_cflags(s->base.tb) & CF_PARALLEL) {
gen_helper_exit_atomic(cpu_env);
} else {
gen_helper_cas2w(cpu_env, regs, addr1, addr2);
}
tcg_temp_free(regs);
/* Note that cas2w also assigned to env->cc_op. */
s->cc_op = CC_OP_CMPW;
s->cc_op_synced = 1;
}
DISAS_INSN(cas2l)
{
uint16_t ext1, ext2;
TCGv addr1, addr2, regs;
/* cas2 Dc1:Dc2,Du1:Du2,(Rn1):(Rn2) */
ext1 = read_im16(env, s);
if (ext1 & 0x8000) {
/* Address Register */
addr1 = AREG(ext1, 12);
} else {
/* Data Register */
addr1 = DREG(ext1, 12);
}
ext2 = read_im16(env, s);
if (ext2 & 0x8000) {
/* Address Register */
addr2 = AREG(ext2, 12);
} else {
/* Data Register */
addr2 = DREG(ext2, 12);
}
/*
* if (R1) == Dc1 && (R2) == Dc2 then
* (R1) = Du1
* (R2) = Du2
* else
* Dc1 = (R1)
* Dc2 = (R2)
*/
regs = tcg_const_i32(REG(ext2, 6) |
(REG(ext1, 6) << 3) |
(REG(ext2, 0) << 6) |
(REG(ext1, 0) << 9));
if (tb_cflags(s->base.tb) & CF_PARALLEL) {
gen_helper_cas2l_parallel(cpu_env, regs, addr1, addr2);
} else {
gen_helper_cas2l(cpu_env, regs, addr1, addr2);
}
tcg_temp_free(regs);
/* Note that cas2l also assigned to env->cc_op. */
s->cc_op = CC_OP_CMPL;
s->cc_op_synced = 1;
}
DISAS_INSN(byterev)
{
TCGv reg;
reg = DREG(insn, 0);
tcg_gen_bswap32_i32(reg, reg);
}
DISAS_INSN(move)
{
TCGv src;
TCGv dest;
int op;
int opsize;
switch (insn >> 12) {
case 1: /* move.b */
opsize = OS_BYTE;
break;
case 2: /* move.l */
opsize = OS_LONG;
break;
case 3: /* move.w */
opsize = OS_WORD;
break;
default:
abort();
}
SRC_EA(env, src, opsize, 1, NULL);
op = (insn >> 6) & 7;
if (op == 1) {
/* movea */
/* The value will already have been sign extended. */
dest = AREG(insn, 9);
tcg_gen_mov_i32(dest, src);
} else {
/* normal move */
uint16_t dest_ea;
dest_ea = ((insn >> 9) & 7) | (op << 3);
DEST_EA(env, dest_ea, opsize, src, NULL);
/* This will be correct because loads sign extend. */
gen_logic_cc(s, src, opsize);
}
}
DISAS_INSN(negx)
{
TCGv z;
TCGv src;
TCGv addr;
int opsize;
opsize = insn_opsize(insn);
SRC_EA(env, src, opsize, 1, &addr);
gen_flush_flags(s); /* compute old Z */
/*
* Perform substract with borrow.
* (X, N) = -(src + X);
*/
z = tcg_const_i32(0);
tcg_gen_add2_i32(QREG_CC_N, QREG_CC_X, src, z, QREG_CC_X, z);
tcg_gen_sub2_i32(QREG_CC_N, QREG_CC_X, z, z, QREG_CC_N, QREG_CC_X);
tcg_temp_free(z);
gen_ext(QREG_CC_N, QREG_CC_N, opsize, 1);
tcg_gen_andi_i32(QREG_CC_X, QREG_CC_X, 1);
/*
* Compute signed-overflow for negation. The normal formula for
* subtraction is (res ^ src) & (src ^ dest), but with dest==0
* this simplies to res & src.
*/
tcg_gen_and_i32(QREG_CC_V, QREG_CC_N, src);
/* Copy the rest of the results into place. */
tcg_gen_or_i32(QREG_CC_Z, QREG_CC_Z, QREG_CC_N); /* !Z is sticky */
tcg_gen_mov_i32(QREG_CC_C, QREG_CC_X);
set_cc_op(s, CC_OP_FLAGS);
/* result is in QREG_CC_N */
DEST_EA(env, insn, opsize, QREG_CC_N, &addr);
}
DISAS_INSN(lea)
{
TCGv reg;
TCGv tmp;
reg = AREG(insn, 9);
tmp = gen_lea(env, s, insn, OS_LONG);
if (IS_NULL_QREG(tmp)) {
gen_addr_fault(s);
return;
}
tcg_gen_mov_i32(reg, tmp);
}
DISAS_INSN(clr)
{
int opsize;
TCGv zero;
zero = tcg_const_i32(0);
opsize = insn_opsize(insn);
DEST_EA(env, insn, opsize, zero, NULL);
gen_logic_cc(s, zero, opsize);
tcg_temp_free(zero);
}
DISAS_INSN(move_from_ccr)
{
TCGv ccr;
ccr = gen_get_ccr(s);
DEST_EA(env, insn, OS_WORD, ccr, NULL);
}
DISAS_INSN(neg)
{
TCGv src1;
TCGv dest;
TCGv addr;
int opsize;
opsize = insn_opsize(insn);
SRC_EA(env, src1, opsize, 1, &addr);
dest = tcg_temp_new();
tcg_gen_neg_i32(dest, src1);
set_cc_op(s, CC_OP_SUBB + opsize);
gen_update_cc_add(dest, src1, opsize);
tcg_gen_setcondi_i32(TCG_COND_NE, QREG_CC_X, dest, 0);
DEST_EA(env, insn, opsize, dest, &addr);
tcg_temp_free(dest);
}
DISAS_INSN(move_to_ccr)
{
gen_move_to_sr(env, s, insn, true);
}
DISAS_INSN(not)
{
TCGv src1;
TCGv dest;
TCGv addr;
int opsize;
opsize = insn_opsize(insn);
SRC_EA(env, src1, opsize, 1, &addr);
dest = tcg_temp_new();
tcg_gen_not_i32(dest, src1);
DEST_EA(env, insn, opsize, dest, &addr);
gen_logic_cc(s, dest, opsize);
}
DISAS_INSN(swap)
{
TCGv src1;
TCGv src2;
TCGv reg;
src1 = tcg_temp_new();
src2 = tcg_temp_new();
reg = DREG(insn, 0);
tcg_gen_shli_i32(src1, reg, 16);
tcg_gen_shri_i32(src2, reg, 16);
tcg_gen_or_i32(reg, src1, src2);
tcg_temp_free(src2);
tcg_temp_free(src1);
gen_logic_cc(s, reg, OS_LONG);
}
DISAS_INSN(bkpt)
{
gen_exception(s, s->base.pc_next, EXCP_DEBUG);
}
DISAS_INSN(pea)
{
TCGv tmp;
tmp = gen_lea(env, s, insn, OS_LONG);
if (IS_NULL_QREG(tmp)) {
gen_addr_fault(s);
return;
}
gen_push(s, tmp);
}
DISAS_INSN(ext)
{
int op;
TCGv reg;
TCGv tmp;
reg = DREG(insn, 0);
op = (insn >> 6) & 7;
tmp = tcg_temp_new();
if (op == 3)
tcg_gen_ext16s_i32(tmp, reg);
else
tcg_gen_ext8s_i32(tmp, reg);
if (op == 2)
gen_partset_reg(OS_WORD, reg, tmp);
else
tcg_gen_mov_i32(reg, tmp);
gen_logic_cc(s, tmp, OS_LONG);
tcg_temp_free(tmp);
}
DISAS_INSN(tst)
{
int opsize;
TCGv tmp;
opsize = insn_opsize(insn);
SRC_EA(env, tmp, opsize, 1, NULL);
gen_logic_cc(s, tmp, opsize);
}
DISAS_INSN(pulse)
{
/* Implemented as a NOP. */
}
DISAS_INSN(illegal)
{
gen_exception(s, s->base.pc_next, EXCP_ILLEGAL);
}
/* ??? This should be atomic. */
DISAS_INSN(tas)
{
TCGv dest;
TCGv src1;
TCGv addr;
dest = tcg_temp_new();
SRC_EA(env, src1, OS_BYTE, 1, &addr);
gen_logic_cc(s, src1, OS_BYTE);
tcg_gen_ori_i32(dest, src1, 0x80);
DEST_EA(env, insn, OS_BYTE, dest, &addr);
tcg_temp_free(dest);
}
DISAS_INSN(mull)
{
uint16_t ext;
TCGv src1;
int sign;
ext = read_im16(env, s);
sign = ext & 0x800;
if (ext & 0x400) {
if (!m68k_feature(s->env, M68K_FEATURE_QUAD_MULDIV)) {
gen_exception(s, s->base.pc_next, EXCP_ILLEGAL);
return;
}
SRC_EA(env, src1, OS_LONG, 0, NULL);
if (sign) {
tcg_gen_muls2_i32(QREG_CC_Z, QREG_CC_N, src1, DREG(ext, 12));
} else {
tcg_gen_mulu2_i32(QREG_CC_Z, QREG_CC_N, src1, DREG(ext, 12));
}
/* if Dl == Dh, 68040 returns low word */
tcg_gen_mov_i32(DREG(ext, 0), QREG_CC_N);
tcg_gen_mov_i32(DREG(ext, 12), QREG_CC_Z);
tcg_gen_or_i32(QREG_CC_Z, QREG_CC_Z, QREG_CC_N);
tcg_gen_movi_i32(QREG_CC_V, 0);
tcg_gen_movi_i32(QREG_CC_C, 0);
set_cc_op(s, CC_OP_FLAGS);
return;
}
SRC_EA(env, src1, OS_LONG, 0, NULL);
if (m68k_feature(s->env, M68K_FEATURE_M68000)) {
tcg_gen_movi_i32(QREG_CC_C, 0);
if (sign) {
tcg_gen_muls2_i32(QREG_CC_N, QREG_CC_V, src1, DREG(ext, 12));
/* QREG_CC_V is -(QREG_CC_V != (QREG_CC_N >> 31)) */
tcg_gen_sari_i32(QREG_CC_Z, QREG_CC_N, 31);
tcg_gen_setcond_i32(TCG_COND_NE, QREG_CC_V, QREG_CC_V, QREG_CC_Z);
} else {
tcg_gen_mulu2_i32(QREG_CC_N, QREG_CC_V, src1, DREG(ext, 12));
/* QREG_CC_V is -(QREG_CC_V != 0), use QREG_CC_C as 0 */
tcg_gen_setcond_i32(TCG_COND_NE, QREG_CC_V, QREG_CC_V, QREG_CC_C);
}
tcg_gen_neg_i32(QREG_CC_V, QREG_CC_V);
tcg_gen_mov_i32(DREG(ext, 12), QREG_CC_N);
tcg_gen_mov_i32(QREG_CC_Z, QREG_CC_N);
set_cc_op(s, CC_OP_FLAGS);
} else {
/*
* The upper 32 bits of the product are discarded, so
* muls.l and mulu.l are functionally equivalent.
*/
tcg_gen_mul_i32(DREG(ext, 12), src1, DREG(ext, 12));
gen_logic_cc(s, DREG(ext, 12), OS_LONG);
}
}
static void gen_link(DisasContext *s, uint16_t insn, int32_t offset)
{
TCGv reg;
TCGv tmp;
reg = AREG(insn, 0);
tmp = tcg_temp_new();
tcg_gen_subi_i32(tmp, QREG_SP, 4);
gen_store(s, OS_LONG, tmp, reg, IS_USER(s));
if ((insn & 7) != 7) {
tcg_gen_mov_i32(reg, tmp);
}
tcg_gen_addi_i32(QREG_SP, tmp, offset);
tcg_temp_free(tmp);
}
DISAS_INSN(link)
{
int16_t offset;
offset = read_im16(env, s);
gen_link(s, insn, offset);
}
DISAS_INSN(linkl)
{
int32_t offset;
offset = read_im32(env, s);
gen_link(s, insn, offset);
}
DISAS_INSN(unlk)
{
TCGv src;
TCGv reg;
TCGv tmp;
src = tcg_temp_new();
reg = AREG(insn, 0);
tcg_gen_mov_i32(src, reg);
tmp = gen_load(s, OS_LONG, src, 0, IS_USER(s));
tcg_gen_mov_i32(reg, tmp);
tcg_gen_addi_i32(QREG_SP, src, 4);
tcg_temp_free(src);
tcg_temp_free(tmp);
}
#if defined(CONFIG_SOFTMMU)
DISAS_INSN(reset)
{
if (IS_USER(s)) {
gen_exception(s, s->base.pc_next, EXCP_PRIVILEGE);
return;
}
gen_helper_reset(cpu_env);
}
#endif
DISAS_INSN(nop)
{
}
DISAS_INSN(rtd)
{
TCGv tmp;
int16_t offset = read_im16(env, s);
tmp = gen_load(s, OS_LONG, QREG_SP, 0, IS_USER(s));
tcg_gen_addi_i32(QREG_SP, QREG_SP, offset + 4);
gen_jmp(s, tmp);
}
DISAS_INSN(rts)
{
TCGv tmp;
tmp = gen_load(s, OS_LONG, QREG_SP, 0, IS_USER(s));
tcg_gen_addi_i32(QREG_SP, QREG_SP, 4);
gen_jmp(s, tmp);
}
DISAS_INSN(jump)
{
TCGv tmp;
/*
* Load the target address first to ensure correct exception
* behavior.
*/
tmp = gen_lea(env, s, insn, OS_LONG);
if (IS_NULL_QREG(tmp)) {
gen_addr_fault(s);
return;
}
if ((insn & 0x40) == 0) {
/* jsr */
gen_push(s, tcg_const_i32(s->pc));
}
gen_jmp(s, tmp);
}
DISAS_INSN(addsubq)
{
TCGv src;
TCGv dest;
TCGv val;
int imm;
TCGv addr;
int opsize;
if ((insn & 070) == 010) {
/* Operation on address register is always long. */
opsize = OS_LONG;
} else {
opsize = insn_opsize(insn);
}
SRC_EA(env, src, opsize, 1, &addr);