4137cb83fa
Signed-off-by: Richard Henderson <rth@twiddle.net>
3940 lines
112 KiB
C
3940 lines
112 KiB
C
/*
|
|
* HPPA emulation cpu translation for qemu.
|
|
*
|
|
* Copyright (c) 2016 Richard Henderson <rth@twiddle.net>
|
|
*
|
|
* 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 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 "qemu/host-utils.h"
|
|
#include "exec/exec-all.h"
|
|
#include "tcg-op.h"
|
|
#include "exec/cpu_ldst.h"
|
|
|
|
#include "exec/helper-proto.h"
|
|
#include "exec/helper-gen.h"
|
|
|
|
#include "trace-tcg.h"
|
|
#include "exec/log.h"
|
|
|
|
typedef struct DisasCond {
|
|
TCGCond c;
|
|
TCGv a0, a1;
|
|
bool a0_is_n;
|
|
bool a1_is_0;
|
|
} DisasCond;
|
|
|
|
typedef struct DisasContext {
|
|
struct TranslationBlock *tb;
|
|
CPUState *cs;
|
|
|
|
target_ulong iaoq_f;
|
|
target_ulong iaoq_b;
|
|
target_ulong iaoq_n;
|
|
TCGv iaoq_n_var;
|
|
|
|
int ntemps;
|
|
TCGv temps[8];
|
|
|
|
DisasCond null_cond;
|
|
TCGLabel *null_lab;
|
|
|
|
bool singlestep_enabled;
|
|
bool psw_n_nonzero;
|
|
} DisasContext;
|
|
|
|
/* Return values from translate_one, indicating the state of the TB.
|
|
Note that zero indicates that we are not exiting the TB. */
|
|
|
|
typedef enum {
|
|
NO_EXIT,
|
|
|
|
/* We have emitted one or more goto_tb. No fixup required. */
|
|
EXIT_GOTO_TB,
|
|
|
|
/* We are not using a goto_tb (for whatever reason), but have updated
|
|
the iaq (for whatever reason), so don't do it again on exit. */
|
|
EXIT_IAQ_N_UPDATED,
|
|
|
|
/* We are exiting the TB, but have neither emitted a goto_tb, nor
|
|
updated the iaq for the next instruction to be executed. */
|
|
EXIT_IAQ_N_STALE,
|
|
|
|
/* We are ending the TB with a noreturn function call, e.g. longjmp.
|
|
No following code will be executed. */
|
|
EXIT_NORETURN,
|
|
} ExitStatus;
|
|
|
|
typedef struct DisasInsn {
|
|
uint32_t insn, mask;
|
|
ExitStatus (*trans)(DisasContext *ctx, uint32_t insn,
|
|
const struct DisasInsn *f);
|
|
union {
|
|
void (*ttt)(TCGv, TCGv, TCGv);
|
|
void (*weww)(TCGv_i32, TCGv_env, TCGv_i32, TCGv_i32);
|
|
void (*dedd)(TCGv_i64, TCGv_env, TCGv_i64, TCGv_i64);
|
|
void (*wew)(TCGv_i32, TCGv_env, TCGv_i32);
|
|
void (*ded)(TCGv_i64, TCGv_env, TCGv_i64);
|
|
void (*wed)(TCGv_i32, TCGv_env, TCGv_i64);
|
|
void (*dew)(TCGv_i64, TCGv_env, TCGv_i32);
|
|
} f;
|
|
} DisasInsn;
|
|
|
|
/* global register indexes */
|
|
static TCGv_env cpu_env;
|
|
static TCGv cpu_gr[32];
|
|
static TCGv cpu_iaoq_f;
|
|
static TCGv cpu_iaoq_b;
|
|
static TCGv cpu_sar;
|
|
static TCGv cpu_psw_n;
|
|
static TCGv cpu_psw_v;
|
|
static TCGv cpu_psw_cb;
|
|
static TCGv cpu_psw_cb_msb;
|
|
static TCGv cpu_cr26;
|
|
static TCGv cpu_cr27;
|
|
|
|
#include "exec/gen-icount.h"
|
|
|
|
void hppa_translate_init(void)
|
|
{
|
|
#define DEF_VAR(V) { &cpu_##V, #V, offsetof(CPUHPPAState, V) }
|
|
|
|
typedef struct { TCGv *var; const char *name; int ofs; } GlobalVar;
|
|
static const GlobalVar vars[] = {
|
|
DEF_VAR(sar),
|
|
DEF_VAR(cr26),
|
|
DEF_VAR(cr27),
|
|
DEF_VAR(psw_n),
|
|
DEF_VAR(psw_v),
|
|
DEF_VAR(psw_cb),
|
|
DEF_VAR(psw_cb_msb),
|
|
DEF_VAR(iaoq_f),
|
|
DEF_VAR(iaoq_b),
|
|
};
|
|
|
|
#undef DEF_VAR
|
|
|
|
/* Use the symbolic register names that match the disassembler. */
|
|
static const char gr_names[32][4] = {
|
|
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
|
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
|
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
|
|
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31"
|
|
};
|
|
|
|
static bool done_init = 0;
|
|
int i;
|
|
|
|
if (done_init) {
|
|
return;
|
|
}
|
|
done_init = 1;
|
|
|
|
cpu_env = tcg_global_reg_new_ptr(TCG_AREG0, "env");
|
|
tcg_ctx.tcg_env = cpu_env;
|
|
|
|
TCGV_UNUSED(cpu_gr[0]);
|
|
for (i = 1; i < 32; i++) {
|
|
cpu_gr[i] = tcg_global_mem_new(cpu_env,
|
|
offsetof(CPUHPPAState, gr[i]),
|
|
gr_names[i]);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vars); ++i) {
|
|
const GlobalVar *v = &vars[i];
|
|
*v->var = tcg_global_mem_new(cpu_env, v->ofs, v->name);
|
|
}
|
|
}
|
|
|
|
static DisasCond cond_make_f(void)
|
|
{
|
|
DisasCond r = { .c = TCG_COND_NEVER };
|
|
TCGV_UNUSED(r.a0);
|
|
TCGV_UNUSED(r.a1);
|
|
return r;
|
|
}
|
|
|
|
static DisasCond cond_make_n(void)
|
|
{
|
|
DisasCond r = { .c = TCG_COND_NE, .a0_is_n = true, .a1_is_0 = true };
|
|
r.a0 = cpu_psw_n;
|
|
TCGV_UNUSED(r.a1);
|
|
return r;
|
|
}
|
|
|
|
static DisasCond cond_make_0(TCGCond c, TCGv a0)
|
|
{
|
|
DisasCond r = { .c = c, .a1_is_0 = true };
|
|
|
|
assert (c != TCG_COND_NEVER && c != TCG_COND_ALWAYS);
|
|
r.a0 = tcg_temp_new();
|
|
tcg_gen_mov_tl(r.a0, a0);
|
|
TCGV_UNUSED(r.a1);
|
|
|
|
return r;
|
|
}
|
|
|
|
static DisasCond cond_make(TCGCond c, TCGv a0, TCGv a1)
|
|
{
|
|
DisasCond r = { .c = c };
|
|
|
|
assert (c != TCG_COND_NEVER && c != TCG_COND_ALWAYS);
|
|
r.a0 = tcg_temp_new();
|
|
tcg_gen_mov_tl(r.a0, a0);
|
|
r.a1 = tcg_temp_new();
|
|
tcg_gen_mov_tl(r.a1, a1);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void cond_prep(DisasCond *cond)
|
|
{
|
|
if (cond->a1_is_0) {
|
|
cond->a1_is_0 = false;
|
|
cond->a1 = tcg_const_tl(0);
|
|
}
|
|
}
|
|
|
|
static void cond_free(DisasCond *cond)
|
|
{
|
|
switch (cond->c) {
|
|
default:
|
|
if (!cond->a0_is_n) {
|
|
tcg_temp_free(cond->a0);
|
|
}
|
|
if (!cond->a1_is_0) {
|
|
tcg_temp_free(cond->a1);
|
|
}
|
|
cond->a0_is_n = false;
|
|
cond->a1_is_0 = false;
|
|
TCGV_UNUSED(cond->a0);
|
|
TCGV_UNUSED(cond->a1);
|
|
/* fallthru */
|
|
case TCG_COND_ALWAYS:
|
|
cond->c = TCG_COND_NEVER;
|
|
break;
|
|
case TCG_COND_NEVER:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static TCGv get_temp(DisasContext *ctx)
|
|
{
|
|
unsigned i = ctx->ntemps++;
|
|
g_assert(i < ARRAY_SIZE(ctx->temps));
|
|
return ctx->temps[i] = tcg_temp_new();
|
|
}
|
|
|
|
static TCGv load_const(DisasContext *ctx, target_long v)
|
|
{
|
|
TCGv t = get_temp(ctx);
|
|
tcg_gen_movi_tl(t, v);
|
|
return t;
|
|
}
|
|
|
|
static TCGv load_gpr(DisasContext *ctx, unsigned reg)
|
|
{
|
|
if (reg == 0) {
|
|
TCGv t = get_temp(ctx);
|
|
tcg_gen_movi_tl(t, 0);
|
|
return t;
|
|
} else {
|
|
return cpu_gr[reg];
|
|
}
|
|
}
|
|
|
|
static TCGv dest_gpr(DisasContext *ctx, unsigned reg)
|
|
{
|
|
if (reg == 0 || ctx->null_cond.c != TCG_COND_NEVER) {
|
|
return get_temp(ctx);
|
|
} else {
|
|
return cpu_gr[reg];
|
|
}
|
|
}
|
|
|
|
static void save_or_nullify(DisasContext *ctx, TCGv dest, TCGv t)
|
|
{
|
|
if (ctx->null_cond.c != TCG_COND_NEVER) {
|
|
cond_prep(&ctx->null_cond);
|
|
tcg_gen_movcond_tl(ctx->null_cond.c, dest, ctx->null_cond.a0,
|
|
ctx->null_cond.a1, dest, t);
|
|
} else {
|
|
tcg_gen_mov_tl(dest, t);
|
|
}
|
|
}
|
|
|
|
static void save_gpr(DisasContext *ctx, unsigned reg, TCGv t)
|
|
{
|
|
if (reg != 0) {
|
|
save_or_nullify(ctx, cpu_gr[reg], t);
|
|
}
|
|
}
|
|
|
|
#ifdef HOST_WORDS_BIGENDIAN
|
|
# define HI_OFS 0
|
|
# define LO_OFS 4
|
|
#else
|
|
# define HI_OFS 4
|
|
# define LO_OFS 0
|
|
#endif
|
|
|
|
static TCGv_i32 load_frw_i32(unsigned rt)
|
|
{
|
|
TCGv_i32 ret = tcg_temp_new_i32();
|
|
tcg_gen_ld_i32(ret, cpu_env,
|
|
offsetof(CPUHPPAState, fr[rt & 31])
|
|
+ (rt & 32 ? LO_OFS : HI_OFS));
|
|
return ret;
|
|
}
|
|
|
|
static TCGv_i32 load_frw0_i32(unsigned rt)
|
|
{
|
|
if (rt == 0) {
|
|
return tcg_const_i32(0);
|
|
} else {
|
|
return load_frw_i32(rt);
|
|
}
|
|
}
|
|
|
|
static TCGv_i64 load_frw0_i64(unsigned rt)
|
|
{
|
|
if (rt == 0) {
|
|
return tcg_const_i64(0);
|
|
} else {
|
|
TCGv_i64 ret = tcg_temp_new_i64();
|
|
tcg_gen_ld32u_i64(ret, cpu_env,
|
|
offsetof(CPUHPPAState, fr[rt & 31])
|
|
+ (rt & 32 ? LO_OFS : HI_OFS));
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static void save_frw_i32(unsigned rt, TCGv_i32 val)
|
|
{
|
|
tcg_gen_st_i32(val, cpu_env,
|
|
offsetof(CPUHPPAState, fr[rt & 31])
|
|
+ (rt & 32 ? LO_OFS : HI_OFS));
|
|
}
|
|
|
|
#undef HI_OFS
|
|
#undef LO_OFS
|
|
|
|
static TCGv_i64 load_frd(unsigned rt)
|
|
{
|
|
TCGv_i64 ret = tcg_temp_new_i64();
|
|
tcg_gen_ld_i64(ret, cpu_env, offsetof(CPUHPPAState, fr[rt]));
|
|
return ret;
|
|
}
|
|
|
|
static TCGv_i64 load_frd0(unsigned rt)
|
|
{
|
|
if (rt == 0) {
|
|
return tcg_const_i64(0);
|
|
} else {
|
|
return load_frd(rt);
|
|
}
|
|
}
|
|
|
|
static void save_frd(unsigned rt, TCGv_i64 val)
|
|
{
|
|
tcg_gen_st_i64(val, cpu_env, offsetof(CPUHPPAState, fr[rt]));
|
|
}
|
|
|
|
/* Skip over the implementation of an insn that has been nullified.
|
|
Use this when the insn is too complex for a conditional move. */
|
|
static void nullify_over(DisasContext *ctx)
|
|
{
|
|
if (ctx->null_cond.c != TCG_COND_NEVER) {
|
|
/* The always condition should have been handled in the main loop. */
|
|
assert(ctx->null_cond.c != TCG_COND_ALWAYS);
|
|
|
|
ctx->null_lab = gen_new_label();
|
|
cond_prep(&ctx->null_cond);
|
|
|
|
/* If we're using PSW[N], copy it to a temp because... */
|
|
if (ctx->null_cond.a0_is_n) {
|
|
ctx->null_cond.a0_is_n = false;
|
|
ctx->null_cond.a0 = tcg_temp_new();
|
|
tcg_gen_mov_tl(ctx->null_cond.a0, cpu_psw_n);
|
|
}
|
|
/* ... we clear it before branching over the implementation,
|
|
so that (1) it's clear after nullifying this insn and
|
|
(2) if this insn nullifies the next, PSW[N] is valid. */
|
|
if (ctx->psw_n_nonzero) {
|
|
ctx->psw_n_nonzero = false;
|
|
tcg_gen_movi_tl(cpu_psw_n, 0);
|
|
}
|
|
|
|
tcg_gen_brcond_tl(ctx->null_cond.c, ctx->null_cond.a0,
|
|
ctx->null_cond.a1, ctx->null_lab);
|
|
cond_free(&ctx->null_cond);
|
|
}
|
|
}
|
|
|
|
/* Save the current nullification state to PSW[N]. */
|
|
static void nullify_save(DisasContext *ctx)
|
|
{
|
|
if (ctx->null_cond.c == TCG_COND_NEVER) {
|
|
if (ctx->psw_n_nonzero) {
|
|
tcg_gen_movi_tl(cpu_psw_n, 0);
|
|
}
|
|
return;
|
|
}
|
|
if (!ctx->null_cond.a0_is_n) {
|
|
cond_prep(&ctx->null_cond);
|
|
tcg_gen_setcond_tl(ctx->null_cond.c, cpu_psw_n,
|
|
ctx->null_cond.a0, ctx->null_cond.a1);
|
|
ctx->psw_n_nonzero = true;
|
|
}
|
|
cond_free(&ctx->null_cond);
|
|
}
|
|
|
|
/* Set a PSW[N] to X. The intention is that this is used immediately
|
|
before a goto_tb/exit_tb, so that there is no fallthru path to other
|
|
code within the TB. Therefore we do not update psw_n_nonzero. */
|
|
static void nullify_set(DisasContext *ctx, bool x)
|
|
{
|
|
if (ctx->psw_n_nonzero || x) {
|
|
tcg_gen_movi_tl(cpu_psw_n, x);
|
|
}
|
|
}
|
|
|
|
/* Mark the end of an instruction that may have been nullified.
|
|
This is the pair to nullify_over. */
|
|
static ExitStatus nullify_end(DisasContext *ctx, ExitStatus status)
|
|
{
|
|
TCGLabel *null_lab = ctx->null_lab;
|
|
|
|
if (likely(null_lab == NULL)) {
|
|
/* The current insn wasn't conditional or handled the condition
|
|
applied to it without a branch, so the (new) setting of
|
|
NULL_COND can be applied directly to the next insn. */
|
|
return status;
|
|
}
|
|
ctx->null_lab = NULL;
|
|
|
|
if (likely(ctx->null_cond.c == TCG_COND_NEVER)) {
|
|
/* The next instruction will be unconditional,
|
|
and NULL_COND already reflects that. */
|
|
gen_set_label(null_lab);
|
|
} else {
|
|
/* The insn that we just executed is itself nullifying the next
|
|
instruction. Store the condition in the PSW[N] global.
|
|
We asserted PSW[N] = 0 in nullify_over, so that after the
|
|
label we have the proper value in place. */
|
|
nullify_save(ctx);
|
|
gen_set_label(null_lab);
|
|
ctx->null_cond = cond_make_n();
|
|
}
|
|
|
|
assert(status != EXIT_GOTO_TB && status != EXIT_IAQ_N_UPDATED);
|
|
if (status == EXIT_NORETURN) {
|
|
status = NO_EXIT;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static void copy_iaoq_entry(TCGv dest, target_ulong ival, TCGv vval)
|
|
{
|
|
if (unlikely(ival == -1)) {
|
|
tcg_gen_mov_tl(dest, vval);
|
|
} else {
|
|
tcg_gen_movi_tl(dest, ival);
|
|
}
|
|
}
|
|
|
|
static inline target_ulong iaoq_dest(DisasContext *ctx, target_long disp)
|
|
{
|
|
return ctx->iaoq_f + disp + 8;
|
|
}
|
|
|
|
static void gen_excp_1(int exception)
|
|
{
|
|
TCGv_i32 t = tcg_const_i32(exception);
|
|
gen_helper_excp(cpu_env, t);
|
|
tcg_temp_free_i32(t);
|
|
}
|
|
|
|
static ExitStatus gen_excp(DisasContext *ctx, int exception)
|
|
{
|
|
copy_iaoq_entry(cpu_iaoq_f, ctx->iaoq_f, cpu_iaoq_f);
|
|
copy_iaoq_entry(cpu_iaoq_b, ctx->iaoq_b, cpu_iaoq_b);
|
|
nullify_save(ctx);
|
|
gen_excp_1(exception);
|
|
return EXIT_NORETURN;
|
|
}
|
|
|
|
static ExitStatus gen_illegal(DisasContext *ctx)
|
|
{
|
|
nullify_over(ctx);
|
|
return nullify_end(ctx, gen_excp(ctx, EXCP_SIGILL));
|
|
}
|
|
|
|
static bool use_goto_tb(DisasContext *ctx, target_ulong dest)
|
|
{
|
|
/* Suppress goto_tb in the case of single-steping and IO. */
|
|
if ((ctx->tb->cflags & CF_LAST_IO) || ctx->singlestep_enabled) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* If the next insn is to be nullified, and it's on the same page,
|
|
and we're not attempting to set a breakpoint on it, then we can
|
|
totally skip the nullified insn. This avoids creating and
|
|
executing a TB that merely branches to the next TB. */
|
|
static bool use_nullify_skip(DisasContext *ctx)
|
|
{
|
|
return (((ctx->iaoq_b ^ ctx->iaoq_f) & TARGET_PAGE_MASK) == 0
|
|
&& !cpu_breakpoint_test(ctx->cs, ctx->iaoq_b, BP_ANY));
|
|
}
|
|
|
|
static void gen_goto_tb(DisasContext *ctx, int which,
|
|
target_ulong f, target_ulong b)
|
|
{
|
|
if (f != -1 && b != -1 && use_goto_tb(ctx, f)) {
|
|
tcg_gen_goto_tb(which);
|
|
tcg_gen_movi_tl(cpu_iaoq_f, f);
|
|
tcg_gen_movi_tl(cpu_iaoq_b, b);
|
|
tcg_gen_exit_tb((uintptr_t)ctx->tb + which);
|
|
} else {
|
|
copy_iaoq_entry(cpu_iaoq_f, f, cpu_iaoq_b);
|
|
copy_iaoq_entry(cpu_iaoq_b, b, ctx->iaoq_n_var);
|
|
if (ctx->singlestep_enabled) {
|
|
gen_excp_1(EXCP_DEBUG);
|
|
} else {
|
|
tcg_gen_lookup_and_goto_ptr(cpu_iaoq_f);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* PA has a habit of taking the LSB of a field and using that as the sign,
|
|
with the rest of the field becoming the least significant bits. */
|
|
static target_long low_sextract(uint32_t val, int pos, int len)
|
|
{
|
|
target_ulong x = -(target_ulong)extract32(val, pos, 1);
|
|
x = (x << (len - 1)) | extract32(val, pos + 1, len - 1);
|
|
return x;
|
|
}
|
|
|
|
static unsigned assemble_rt64(uint32_t insn)
|
|
{
|
|
unsigned r1 = extract32(insn, 6, 1);
|
|
unsigned r0 = extract32(insn, 0, 5);
|
|
return r1 * 32 + r0;
|
|
}
|
|
|
|
static unsigned assemble_ra64(uint32_t insn)
|
|
{
|
|
unsigned r1 = extract32(insn, 7, 1);
|
|
unsigned r0 = extract32(insn, 21, 5);
|
|
return r1 * 32 + r0;
|
|
}
|
|
|
|
static unsigned assemble_rb64(uint32_t insn)
|
|
{
|
|
unsigned r1 = extract32(insn, 12, 1);
|
|
unsigned r0 = extract32(insn, 16, 5);
|
|
return r1 * 32 + r0;
|
|
}
|
|
|
|
static unsigned assemble_rc64(uint32_t insn)
|
|
{
|
|
unsigned r2 = extract32(insn, 8, 1);
|
|
unsigned r1 = extract32(insn, 13, 3);
|
|
unsigned r0 = extract32(insn, 9, 2);
|
|
return r2 * 32 + r1 * 4 + r0;
|
|
}
|
|
|
|
static target_long assemble_12(uint32_t insn)
|
|
{
|
|
target_ulong x = -(target_ulong)(insn & 1);
|
|
x = (x << 1) | extract32(insn, 2, 1);
|
|
x = (x << 10) | extract32(insn, 3, 10);
|
|
return x;
|
|
}
|
|
|
|
static target_long assemble_16(uint32_t insn)
|
|
{
|
|
/* Take the name from PA2.0, which produces a 16-bit number
|
|
only with wide mode; otherwise a 14-bit number. Since we don't
|
|
implement wide mode, this is always the 14-bit number. */
|
|
return low_sextract(insn, 0, 14);
|
|
}
|
|
|
|
static target_long assemble_16a(uint32_t insn)
|
|
{
|
|
/* Take the name from PA2.0, which produces a 14-bit shifted number
|
|
only with wide mode; otherwise a 12-bit shifted number. Since we
|
|
don't implement wide mode, this is always the 12-bit number. */
|
|
target_ulong x = -(target_ulong)(insn & 1);
|
|
x = (x << 11) | extract32(insn, 2, 11);
|
|
return x << 2;
|
|
}
|
|
|
|
static target_long assemble_17(uint32_t insn)
|
|
{
|
|
target_ulong x = -(target_ulong)(insn & 1);
|
|
x = (x << 5) | extract32(insn, 16, 5);
|
|
x = (x << 1) | extract32(insn, 2, 1);
|
|
x = (x << 10) | extract32(insn, 3, 10);
|
|
return x << 2;
|
|
}
|
|
|
|
static target_long assemble_21(uint32_t insn)
|
|
{
|
|
target_ulong x = -(target_ulong)(insn & 1);
|
|
x = (x << 11) | extract32(insn, 1, 11);
|
|
x = (x << 2) | extract32(insn, 14, 2);
|
|
x = (x << 5) | extract32(insn, 16, 5);
|
|
x = (x << 2) | extract32(insn, 12, 2);
|
|
return x << 11;
|
|
}
|
|
|
|
static target_long assemble_22(uint32_t insn)
|
|
{
|
|
target_ulong x = -(target_ulong)(insn & 1);
|
|
x = (x << 10) | extract32(insn, 16, 10);
|
|
x = (x << 1) | extract32(insn, 2, 1);
|
|
x = (x << 10) | extract32(insn, 3, 10);
|
|
return x << 2;
|
|
}
|
|
|
|
/* The parisc documentation describes only the general interpretation of
|
|
the conditions, without describing their exact implementation. The
|
|
interpretations do not stand up well when considering ADD,C and SUB,B.
|
|
However, considering the Addition, Subtraction and Logical conditions
|
|
as a whole it would appear that these relations are similar to what
|
|
a traditional NZCV set of flags would produce. */
|
|
|
|
static DisasCond do_cond(unsigned cf, TCGv res, TCGv cb_msb, TCGv sv)
|
|
{
|
|
DisasCond cond;
|
|
TCGv tmp;
|
|
|
|
switch (cf >> 1) {
|
|
case 0: /* Never / TR */
|
|
cond = cond_make_f();
|
|
break;
|
|
case 1: /* = / <> (Z / !Z) */
|
|
cond = cond_make_0(TCG_COND_EQ, res);
|
|
break;
|
|
case 2: /* < / >= (N / !N) */
|
|
cond = cond_make_0(TCG_COND_LT, res);
|
|
break;
|
|
case 3: /* <= / > (N | Z / !N & !Z) */
|
|
cond = cond_make_0(TCG_COND_LE, res);
|
|
break;
|
|
case 4: /* NUV / UV (!C / C) */
|
|
cond = cond_make_0(TCG_COND_EQ, cb_msb);
|
|
break;
|
|
case 5: /* ZNV / VNZ (!C | Z / C & !Z) */
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_neg_tl(tmp, cb_msb);
|
|
tcg_gen_and_tl(tmp, tmp, res);
|
|
cond = cond_make_0(TCG_COND_EQ, tmp);
|
|
tcg_temp_free(tmp);
|
|
break;
|
|
case 6: /* SV / NSV (V / !V) */
|
|
cond = cond_make_0(TCG_COND_LT, sv);
|
|
break;
|
|
case 7: /* OD / EV */
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_andi_tl(tmp, res, 1);
|
|
cond = cond_make_0(TCG_COND_NE, tmp);
|
|
tcg_temp_free(tmp);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
if (cf & 1) {
|
|
cond.c = tcg_invert_cond(cond.c);
|
|
}
|
|
|
|
return cond;
|
|
}
|
|
|
|
/* Similar, but for the special case of subtraction without borrow, we
|
|
can use the inputs directly. This can allow other computation to be
|
|
deleted as unused. */
|
|
|
|
static DisasCond do_sub_cond(unsigned cf, TCGv res, TCGv in1, TCGv in2, TCGv sv)
|
|
{
|
|
DisasCond cond;
|
|
|
|
switch (cf >> 1) {
|
|
case 1: /* = / <> */
|
|
cond = cond_make(TCG_COND_EQ, in1, in2);
|
|
break;
|
|
case 2: /* < / >= */
|
|
cond = cond_make(TCG_COND_LT, in1, in2);
|
|
break;
|
|
case 3: /* <= / > */
|
|
cond = cond_make(TCG_COND_LE, in1, in2);
|
|
break;
|
|
case 4: /* << / >>= */
|
|
cond = cond_make(TCG_COND_LTU, in1, in2);
|
|
break;
|
|
case 5: /* <<= / >> */
|
|
cond = cond_make(TCG_COND_LEU, in1, in2);
|
|
break;
|
|
default:
|
|
return do_cond(cf, res, sv, sv);
|
|
}
|
|
if (cf & 1) {
|
|
cond.c = tcg_invert_cond(cond.c);
|
|
}
|
|
|
|
return cond;
|
|
}
|
|
|
|
/* Similar, but for logicals, where the carry and overflow bits are not
|
|
computed, and use of them is undefined. */
|
|
|
|
static DisasCond do_log_cond(unsigned cf, TCGv res)
|
|
{
|
|
switch (cf >> 1) {
|
|
case 4: case 5: case 6:
|
|
cf &= 1;
|
|
break;
|
|
}
|
|
return do_cond(cf, res, res, res);
|
|
}
|
|
|
|
/* Similar, but for shift/extract/deposit conditions. */
|
|
|
|
static DisasCond do_sed_cond(unsigned orig, TCGv res)
|
|
{
|
|
unsigned c, f;
|
|
|
|
/* Convert the compressed condition codes to standard.
|
|
0-2 are the same as logicals (nv,<,<=), while 3 is OD.
|
|
4-7 are the reverse of 0-3. */
|
|
c = orig & 3;
|
|
if (c == 3) {
|
|
c = 7;
|
|
}
|
|
f = (orig & 4) / 4;
|
|
|
|
return do_log_cond(c * 2 + f, res);
|
|
}
|
|
|
|
/* Similar, but for unit conditions. */
|
|
|
|
static DisasCond do_unit_cond(unsigned cf, TCGv res, TCGv in1, TCGv in2)
|
|
{
|
|
DisasCond cond;
|
|
TCGv tmp, cb;
|
|
|
|
TCGV_UNUSED(cb);
|
|
if (cf & 8) {
|
|
/* Since we want to test lots of carry-out bits all at once, do not
|
|
* do our normal thing and compute carry-in of bit B+1 since that
|
|
* leaves us with carry bits spread across two words.
|
|
*/
|
|
cb = tcg_temp_new();
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_or_tl(cb, in1, in2);
|
|
tcg_gen_and_tl(tmp, in1, in2);
|
|
tcg_gen_andc_tl(cb, cb, res);
|
|
tcg_gen_or_tl(cb, cb, tmp);
|
|
tcg_temp_free(tmp);
|
|
}
|
|
|
|
switch (cf >> 1) {
|
|
case 0: /* never / TR */
|
|
case 1: /* undefined */
|
|
case 5: /* undefined */
|
|
cond = cond_make_f();
|
|
break;
|
|
|
|
case 2: /* SBZ / NBZ */
|
|
/* See hasless(v,1) from
|
|
* https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
|
|
*/
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_subi_tl(tmp, res, 0x01010101u);
|
|
tcg_gen_andc_tl(tmp, tmp, res);
|
|
tcg_gen_andi_tl(tmp, tmp, 0x80808080u);
|
|
cond = cond_make_0(TCG_COND_NE, tmp);
|
|
tcg_temp_free(tmp);
|
|
break;
|
|
|
|
case 3: /* SHZ / NHZ */
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_subi_tl(tmp, res, 0x00010001u);
|
|
tcg_gen_andc_tl(tmp, tmp, res);
|
|
tcg_gen_andi_tl(tmp, tmp, 0x80008000u);
|
|
cond = cond_make_0(TCG_COND_NE, tmp);
|
|
tcg_temp_free(tmp);
|
|
break;
|
|
|
|
case 4: /* SDC / NDC */
|
|
tcg_gen_andi_tl(cb, cb, 0x88888888u);
|
|
cond = cond_make_0(TCG_COND_NE, cb);
|
|
break;
|
|
|
|
case 6: /* SBC / NBC */
|
|
tcg_gen_andi_tl(cb, cb, 0x80808080u);
|
|
cond = cond_make_0(TCG_COND_NE, cb);
|
|
break;
|
|
|
|
case 7: /* SHC / NHC */
|
|
tcg_gen_andi_tl(cb, cb, 0x80008000u);
|
|
cond = cond_make_0(TCG_COND_NE, cb);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
if (cf & 8) {
|
|
tcg_temp_free(cb);
|
|
}
|
|
if (cf & 1) {
|
|
cond.c = tcg_invert_cond(cond.c);
|
|
}
|
|
|
|
return cond;
|
|
}
|
|
|
|
/* Compute signed overflow for addition. */
|
|
static TCGv do_add_sv(DisasContext *ctx, TCGv res, TCGv in1, TCGv in2)
|
|
{
|
|
TCGv sv = get_temp(ctx);
|
|
TCGv tmp = tcg_temp_new();
|
|
|
|
tcg_gen_xor_tl(sv, res, in1);
|
|
tcg_gen_xor_tl(tmp, in1, in2);
|
|
tcg_gen_andc_tl(sv, sv, tmp);
|
|
tcg_temp_free(tmp);
|
|
|
|
return sv;
|
|
}
|
|
|
|
/* Compute signed overflow for subtraction. */
|
|
static TCGv do_sub_sv(DisasContext *ctx, TCGv res, TCGv in1, TCGv in2)
|
|
{
|
|
TCGv sv = get_temp(ctx);
|
|
TCGv tmp = tcg_temp_new();
|
|
|
|
tcg_gen_xor_tl(sv, res, in1);
|
|
tcg_gen_xor_tl(tmp, in1, in2);
|
|
tcg_gen_and_tl(sv, sv, tmp);
|
|
tcg_temp_free(tmp);
|
|
|
|
return sv;
|
|
}
|
|
|
|
static ExitStatus do_add(DisasContext *ctx, unsigned rt, TCGv in1, TCGv in2,
|
|
unsigned shift, bool is_l, bool is_tsv, bool is_tc,
|
|
bool is_c, unsigned cf)
|
|
{
|
|
TCGv dest, cb, cb_msb, sv, tmp;
|
|
unsigned c = cf >> 1;
|
|
DisasCond cond;
|
|
|
|
dest = tcg_temp_new();
|
|
TCGV_UNUSED(cb);
|
|
TCGV_UNUSED(cb_msb);
|
|
|
|
if (shift) {
|
|
tmp = get_temp(ctx);
|
|
tcg_gen_shli_tl(tmp, in1, shift);
|
|
in1 = tmp;
|
|
}
|
|
|
|
if (!is_l || c == 4 || c == 5) {
|
|
TCGv zero = tcg_const_tl(0);
|
|
cb_msb = get_temp(ctx);
|
|
tcg_gen_add2_tl(dest, cb_msb, in1, zero, in2, zero);
|
|
if (is_c) {
|
|
tcg_gen_add2_tl(dest, cb_msb, dest, cb_msb, cpu_psw_cb_msb, zero);
|
|
}
|
|
tcg_temp_free(zero);
|
|
if (!is_l) {
|
|
cb = get_temp(ctx);
|
|
tcg_gen_xor_tl(cb, in1, in2);
|
|
tcg_gen_xor_tl(cb, cb, dest);
|
|
}
|
|
} else {
|
|
tcg_gen_add_tl(dest, in1, in2);
|
|
if (is_c) {
|
|
tcg_gen_add_tl(dest, dest, cpu_psw_cb_msb);
|
|
}
|
|
}
|
|
|
|
/* Compute signed overflow if required. */
|
|
TCGV_UNUSED(sv);
|
|
if (is_tsv || c == 6) {
|
|
sv = do_add_sv(ctx, dest, in1, in2);
|
|
if (is_tsv) {
|
|
/* ??? Need to include overflow from shift. */
|
|
gen_helper_tsv(cpu_env, sv);
|
|
}
|
|
}
|
|
|
|
/* Emit any conditional trap before any writeback. */
|
|
cond = do_cond(cf, dest, cb_msb, sv);
|
|
if (is_tc) {
|
|
cond_prep(&cond);
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_setcond_tl(cond.c, tmp, cond.a0, cond.a1);
|
|
gen_helper_tcond(cpu_env, tmp);
|
|
tcg_temp_free(tmp);
|
|
}
|
|
|
|
/* Write back the result. */
|
|
if (!is_l) {
|
|
save_or_nullify(ctx, cpu_psw_cb, cb);
|
|
save_or_nullify(ctx, cpu_psw_cb_msb, cb_msb);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
tcg_temp_free(dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
ctx->null_cond = cond;
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus do_sub(DisasContext *ctx, unsigned rt, TCGv in1, TCGv in2,
|
|
bool is_tsv, bool is_b, bool is_tc, unsigned cf)
|
|
{
|
|
TCGv dest, sv, cb, cb_msb, zero, tmp;
|
|
unsigned c = cf >> 1;
|
|
DisasCond cond;
|
|
|
|
dest = tcg_temp_new();
|
|
cb = tcg_temp_new();
|
|
cb_msb = tcg_temp_new();
|
|
|
|
zero = tcg_const_tl(0);
|
|
if (is_b) {
|
|
/* DEST,C = IN1 + ~IN2 + C. */
|
|
tcg_gen_not_tl(cb, in2);
|
|
tcg_gen_add2_tl(dest, cb_msb, in1, zero, cpu_psw_cb_msb, zero);
|
|
tcg_gen_add2_tl(dest, cb_msb, dest, cb_msb, cb, zero);
|
|
tcg_gen_xor_tl(cb, cb, in1);
|
|
tcg_gen_xor_tl(cb, cb, dest);
|
|
} else {
|
|
/* DEST,C = IN1 + ~IN2 + 1. We can produce the same result in fewer
|
|
operations by seeding the high word with 1 and subtracting. */
|
|
tcg_gen_movi_tl(cb_msb, 1);
|
|
tcg_gen_sub2_tl(dest, cb_msb, in1, cb_msb, in2, zero);
|
|
tcg_gen_eqv_tl(cb, in1, in2);
|
|
tcg_gen_xor_tl(cb, cb, dest);
|
|
}
|
|
tcg_temp_free(zero);
|
|
|
|
/* Compute signed overflow if required. */
|
|
TCGV_UNUSED(sv);
|
|
if (is_tsv || c == 6) {
|
|
sv = do_sub_sv(ctx, dest, in1, in2);
|
|
if (is_tsv) {
|
|
gen_helper_tsv(cpu_env, sv);
|
|
}
|
|
}
|
|
|
|
/* Compute the condition. We cannot use the special case for borrow. */
|
|
if (!is_b) {
|
|
cond = do_sub_cond(cf, dest, in1, in2, sv);
|
|
} else {
|
|
cond = do_cond(cf, dest, cb_msb, sv);
|
|
}
|
|
|
|
/* Emit any conditional trap before any writeback. */
|
|
if (is_tc) {
|
|
cond_prep(&cond);
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_setcond_tl(cond.c, tmp, cond.a0, cond.a1);
|
|
gen_helper_tcond(cpu_env, tmp);
|
|
tcg_temp_free(tmp);
|
|
}
|
|
|
|
/* Write back the result. */
|
|
save_or_nullify(ctx, cpu_psw_cb, cb);
|
|
save_or_nullify(ctx, cpu_psw_cb_msb, cb_msb);
|
|
save_gpr(ctx, rt, dest);
|
|
tcg_temp_free(dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
ctx->null_cond = cond;
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus do_cmpclr(DisasContext *ctx, unsigned rt, TCGv in1,
|
|
TCGv in2, unsigned cf)
|
|
{
|
|
TCGv dest, sv;
|
|
DisasCond cond;
|
|
|
|
dest = tcg_temp_new();
|
|
tcg_gen_sub_tl(dest, in1, in2);
|
|
|
|
/* Compute signed overflow if required. */
|
|
TCGV_UNUSED(sv);
|
|
if ((cf >> 1) == 6) {
|
|
sv = do_sub_sv(ctx, dest, in1, in2);
|
|
}
|
|
|
|
/* Form the condition for the compare. */
|
|
cond = do_sub_cond(cf, dest, in1, in2, sv);
|
|
|
|
/* Clear. */
|
|
tcg_gen_movi_tl(dest, 0);
|
|
save_gpr(ctx, rt, dest);
|
|
tcg_temp_free(dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
ctx->null_cond = cond;
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus do_log(DisasContext *ctx, unsigned rt, TCGv in1, TCGv in2,
|
|
unsigned cf, void (*fn)(TCGv, TCGv, TCGv))
|
|
{
|
|
TCGv dest = dest_gpr(ctx, rt);
|
|
|
|
/* Perform the operation, and writeback. */
|
|
fn(dest, in1, in2);
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (cf) {
|
|
ctx->null_cond = do_log_cond(cf, dest);
|
|
}
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus do_unit(DisasContext *ctx, unsigned rt, TCGv in1,
|
|
TCGv in2, unsigned cf, bool is_tc,
|
|
void (*fn)(TCGv, TCGv, TCGv))
|
|
{
|
|
TCGv dest;
|
|
DisasCond cond;
|
|
|
|
if (cf == 0) {
|
|
dest = dest_gpr(ctx, rt);
|
|
fn(dest, in1, in2);
|
|
save_gpr(ctx, rt, dest);
|
|
cond_free(&ctx->null_cond);
|
|
} else {
|
|
dest = tcg_temp_new();
|
|
fn(dest, in1, in2);
|
|
|
|
cond = do_unit_cond(cf, dest, in1, in2);
|
|
|
|
if (is_tc) {
|
|
TCGv tmp = tcg_temp_new();
|
|
cond_prep(&cond);
|
|
tcg_gen_setcond_tl(cond.c, tmp, cond.a0, cond.a1);
|
|
gen_helper_tcond(cpu_env, tmp);
|
|
tcg_temp_free(tmp);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
ctx->null_cond = cond;
|
|
}
|
|
return NO_EXIT;
|
|
}
|
|
|
|
/* Emit a memory load. The modify parameter should be
|
|
* < 0 for pre-modify,
|
|
* > 0 for post-modify,
|
|
* = 0 for no base register update.
|
|
*/
|
|
static void do_load_32(DisasContext *ctx, TCGv_i32 dest, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify, TCGMemOp mop)
|
|
{
|
|
TCGv addr, base;
|
|
|
|
/* Caller uses nullify_over/nullify_end. */
|
|
assert(ctx->null_cond.c == TCG_COND_NEVER);
|
|
|
|
addr = tcg_temp_new();
|
|
base = load_gpr(ctx, rb);
|
|
|
|
/* Note that RX is mutually exclusive with DISP. */
|
|
if (rx) {
|
|
tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
|
|
tcg_gen_add_tl(addr, addr, base);
|
|
} else {
|
|
tcg_gen_addi_tl(addr, base, disp);
|
|
}
|
|
|
|
if (modify == 0) {
|
|
tcg_gen_qemu_ld_i32(dest, addr, MMU_USER_IDX, mop);
|
|
} else {
|
|
tcg_gen_qemu_ld_i32(dest, (modify < 0 ? addr : base),
|
|
MMU_USER_IDX, mop);
|
|
save_gpr(ctx, rb, addr);
|
|
}
|
|
tcg_temp_free(addr);
|
|
}
|
|
|
|
static void do_load_64(DisasContext *ctx, TCGv_i64 dest, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify, TCGMemOp mop)
|
|
{
|
|
TCGv addr, base;
|
|
|
|
/* Caller uses nullify_over/nullify_end. */
|
|
assert(ctx->null_cond.c == TCG_COND_NEVER);
|
|
|
|
addr = tcg_temp_new();
|
|
base = load_gpr(ctx, rb);
|
|
|
|
/* Note that RX is mutually exclusive with DISP. */
|
|
if (rx) {
|
|
tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
|
|
tcg_gen_add_tl(addr, addr, base);
|
|
} else {
|
|
tcg_gen_addi_tl(addr, base, disp);
|
|
}
|
|
|
|
if (modify == 0) {
|
|
tcg_gen_qemu_ld_i64(dest, addr, MMU_USER_IDX, mop);
|
|
} else {
|
|
tcg_gen_qemu_ld_i64(dest, (modify < 0 ? addr : base),
|
|
MMU_USER_IDX, mop);
|
|
save_gpr(ctx, rb, addr);
|
|
}
|
|
tcg_temp_free(addr);
|
|
}
|
|
|
|
static void do_store_32(DisasContext *ctx, TCGv_i32 src, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify, TCGMemOp mop)
|
|
{
|
|
TCGv addr, base;
|
|
|
|
/* Caller uses nullify_over/nullify_end. */
|
|
assert(ctx->null_cond.c == TCG_COND_NEVER);
|
|
|
|
addr = tcg_temp_new();
|
|
base = load_gpr(ctx, rb);
|
|
|
|
/* Note that RX is mutually exclusive with DISP. */
|
|
if (rx) {
|
|
tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
|
|
tcg_gen_add_tl(addr, addr, base);
|
|
} else {
|
|
tcg_gen_addi_tl(addr, base, disp);
|
|
}
|
|
|
|
tcg_gen_qemu_st_i32(src, (modify <= 0 ? addr : base), MMU_USER_IDX, mop);
|
|
|
|
if (modify != 0) {
|
|
save_gpr(ctx, rb, addr);
|
|
}
|
|
tcg_temp_free(addr);
|
|
}
|
|
|
|
static void do_store_64(DisasContext *ctx, TCGv_i64 src, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify, TCGMemOp mop)
|
|
{
|
|
TCGv addr, base;
|
|
|
|
/* Caller uses nullify_over/nullify_end. */
|
|
assert(ctx->null_cond.c == TCG_COND_NEVER);
|
|
|
|
addr = tcg_temp_new();
|
|
base = load_gpr(ctx, rb);
|
|
|
|
/* Note that RX is mutually exclusive with DISP. */
|
|
if (rx) {
|
|
tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
|
|
tcg_gen_add_tl(addr, addr, base);
|
|
} else {
|
|
tcg_gen_addi_tl(addr, base, disp);
|
|
}
|
|
|
|
tcg_gen_qemu_st_i64(src, (modify <= 0 ? addr : base), MMU_USER_IDX, mop);
|
|
|
|
if (modify != 0) {
|
|
save_gpr(ctx, rb, addr);
|
|
}
|
|
tcg_temp_free(addr);
|
|
}
|
|
|
|
#if TARGET_LONG_BITS == 64
|
|
#define do_load_tl do_load_64
|
|
#define do_store_tl do_store_64
|
|
#else
|
|
#define do_load_tl do_load_32
|
|
#define do_store_tl do_store_32
|
|
#endif
|
|
|
|
static ExitStatus do_load(DisasContext *ctx, unsigned rt, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify, TCGMemOp mop)
|
|
{
|
|
TCGv dest;
|
|
|
|
nullify_over(ctx);
|
|
|
|
if (modify == 0) {
|
|
/* No base register update. */
|
|
dest = dest_gpr(ctx, rt);
|
|
} else {
|
|
/* Make sure if RT == RB, we see the result of the load. */
|
|
dest = get_temp(ctx);
|
|
}
|
|
do_load_tl(ctx, dest, rb, rx, scale, disp, modify, mop);
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_floadw(DisasContext *ctx, unsigned rt, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify)
|
|
{
|
|
TCGv_i32 tmp;
|
|
|
|
nullify_over(ctx);
|
|
|
|
tmp = tcg_temp_new_i32();
|
|
do_load_32(ctx, tmp, rb, rx, scale, disp, modify, MO_TEUL);
|
|
save_frw_i32(rt, tmp);
|
|
tcg_temp_free_i32(tmp);
|
|
|
|
if (rt == 0) {
|
|
gen_helper_loaded_fr0(cpu_env);
|
|
}
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_floadd(DisasContext *ctx, unsigned rt, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify)
|
|
{
|
|
TCGv_i64 tmp;
|
|
|
|
nullify_over(ctx);
|
|
|
|
tmp = tcg_temp_new_i64();
|
|
do_load_64(ctx, tmp, rb, rx, scale, disp, modify, MO_TEQ);
|
|
save_frd(rt, tmp);
|
|
tcg_temp_free_i64(tmp);
|
|
|
|
if (rt == 0) {
|
|
gen_helper_loaded_fr0(cpu_env);
|
|
}
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_store(DisasContext *ctx, unsigned rt, unsigned rb,
|
|
target_long disp, int modify, TCGMemOp mop)
|
|
{
|
|
nullify_over(ctx);
|
|
do_store_tl(ctx, load_gpr(ctx, rt), rb, 0, 0, disp, modify, mop);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fstorew(DisasContext *ctx, unsigned rt, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify)
|
|
{
|
|
TCGv_i32 tmp;
|
|
|
|
nullify_over(ctx);
|
|
|
|
tmp = load_frw_i32(rt);
|
|
do_store_32(ctx, tmp, rb, rx, scale, disp, modify, MO_TEUL);
|
|
tcg_temp_free_i32(tmp);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fstored(DisasContext *ctx, unsigned rt, unsigned rb,
|
|
unsigned rx, int scale, target_long disp,
|
|
int modify)
|
|
{
|
|
TCGv_i64 tmp;
|
|
|
|
nullify_over(ctx);
|
|
|
|
tmp = load_frd(rt);
|
|
do_store_64(ctx, tmp, rb, rx, scale, disp, modify, MO_TEQ);
|
|
tcg_temp_free_i64(tmp);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fop_wew(DisasContext *ctx, unsigned rt, unsigned ra,
|
|
void (*func)(TCGv_i32, TCGv_env, TCGv_i32))
|
|
{
|
|
TCGv_i32 tmp;
|
|
|
|
nullify_over(ctx);
|
|
tmp = load_frw0_i32(ra);
|
|
|
|
func(tmp, cpu_env, tmp);
|
|
|
|
save_frw_i32(rt, tmp);
|
|
tcg_temp_free_i32(tmp);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fop_wed(DisasContext *ctx, unsigned rt, unsigned ra,
|
|
void (*func)(TCGv_i32, TCGv_env, TCGv_i64))
|
|
{
|
|
TCGv_i32 dst;
|
|
TCGv_i64 src;
|
|
|
|
nullify_over(ctx);
|
|
src = load_frd(ra);
|
|
dst = tcg_temp_new_i32();
|
|
|
|
func(dst, cpu_env, src);
|
|
|
|
tcg_temp_free_i64(src);
|
|
save_frw_i32(rt, dst);
|
|
tcg_temp_free_i32(dst);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fop_ded(DisasContext *ctx, unsigned rt, unsigned ra,
|
|
void (*func)(TCGv_i64, TCGv_env, TCGv_i64))
|
|
{
|
|
TCGv_i64 tmp;
|
|
|
|
nullify_over(ctx);
|
|
tmp = load_frd0(ra);
|
|
|
|
func(tmp, cpu_env, tmp);
|
|
|
|
save_frd(rt, tmp);
|
|
tcg_temp_free_i64(tmp);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fop_dew(DisasContext *ctx, unsigned rt, unsigned ra,
|
|
void (*func)(TCGv_i64, TCGv_env, TCGv_i32))
|
|
{
|
|
TCGv_i32 src;
|
|
TCGv_i64 dst;
|
|
|
|
nullify_over(ctx);
|
|
src = load_frw0_i32(ra);
|
|
dst = tcg_temp_new_i64();
|
|
|
|
func(dst, cpu_env, src);
|
|
|
|
tcg_temp_free_i32(src);
|
|
save_frd(rt, dst);
|
|
tcg_temp_free_i64(dst);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fop_weww(DisasContext *ctx, unsigned rt,
|
|
unsigned ra, unsigned rb,
|
|
void (*func)(TCGv_i32, TCGv_env,
|
|
TCGv_i32, TCGv_i32))
|
|
{
|
|
TCGv_i32 a, b;
|
|
|
|
nullify_over(ctx);
|
|
a = load_frw0_i32(ra);
|
|
b = load_frw0_i32(rb);
|
|
|
|
func(a, cpu_env, a, b);
|
|
|
|
tcg_temp_free_i32(b);
|
|
save_frw_i32(rt, a);
|
|
tcg_temp_free_i32(a);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus do_fop_dedd(DisasContext *ctx, unsigned rt,
|
|
unsigned ra, unsigned rb,
|
|
void (*func)(TCGv_i64, TCGv_env,
|
|
TCGv_i64, TCGv_i64))
|
|
{
|
|
TCGv_i64 a, b;
|
|
|
|
nullify_over(ctx);
|
|
a = load_frd0(ra);
|
|
b = load_frd0(rb);
|
|
|
|
func(a, cpu_env, a, b);
|
|
|
|
tcg_temp_free_i64(b);
|
|
save_frd(rt, a);
|
|
tcg_temp_free_i64(a);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
/* Emit an unconditional branch to a direct target, which may or may not
|
|
have already had nullification handled. */
|
|
static ExitStatus do_dbranch(DisasContext *ctx, target_ulong dest,
|
|
unsigned link, bool is_n)
|
|
{
|
|
if (ctx->null_cond.c == TCG_COND_NEVER && ctx->null_lab == NULL) {
|
|
if (link != 0) {
|
|
copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
|
|
}
|
|
ctx->iaoq_n = dest;
|
|
if (is_n) {
|
|
ctx->null_cond.c = TCG_COND_ALWAYS;
|
|
}
|
|
return NO_EXIT;
|
|
} else {
|
|
nullify_over(ctx);
|
|
|
|
if (link != 0) {
|
|
copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
|
|
}
|
|
|
|
if (is_n && use_nullify_skip(ctx)) {
|
|
nullify_set(ctx, 0);
|
|
gen_goto_tb(ctx, 0, dest, dest + 4);
|
|
} else {
|
|
nullify_set(ctx, is_n);
|
|
gen_goto_tb(ctx, 0, ctx->iaoq_b, dest);
|
|
}
|
|
|
|
nullify_end(ctx, NO_EXIT);
|
|
|
|
nullify_set(ctx, 0);
|
|
gen_goto_tb(ctx, 1, ctx->iaoq_b, ctx->iaoq_n);
|
|
return EXIT_GOTO_TB;
|
|
}
|
|
}
|
|
|
|
/* Emit a conditional branch to a direct target. If the branch itself
|
|
is nullified, we should have already used nullify_over. */
|
|
static ExitStatus do_cbranch(DisasContext *ctx, target_long disp, bool is_n,
|
|
DisasCond *cond)
|
|
{
|
|
target_ulong dest = iaoq_dest(ctx, disp);
|
|
TCGLabel *taken = NULL;
|
|
TCGCond c = cond->c;
|
|
bool n;
|
|
|
|
assert(ctx->null_cond.c == TCG_COND_NEVER);
|
|
|
|
/* Handle TRUE and NEVER as direct branches. */
|
|
if (c == TCG_COND_ALWAYS) {
|
|
return do_dbranch(ctx, dest, 0, is_n && disp >= 0);
|
|
}
|
|
if (c == TCG_COND_NEVER) {
|
|
return do_dbranch(ctx, ctx->iaoq_n, 0, is_n && disp < 0);
|
|
}
|
|
|
|
taken = gen_new_label();
|
|
cond_prep(cond);
|
|
tcg_gen_brcond_tl(c, cond->a0, cond->a1, taken);
|
|
cond_free(cond);
|
|
|
|
/* Not taken: Condition not satisfied; nullify on backward branches. */
|
|
n = is_n && disp < 0;
|
|
if (n && use_nullify_skip(ctx)) {
|
|
nullify_set(ctx, 0);
|
|
gen_goto_tb(ctx, 0, ctx->iaoq_n, ctx->iaoq_n + 4);
|
|
} else {
|
|
if (!n && ctx->null_lab) {
|
|
gen_set_label(ctx->null_lab);
|
|
ctx->null_lab = NULL;
|
|
}
|
|
nullify_set(ctx, n);
|
|
gen_goto_tb(ctx, 0, ctx->iaoq_b, ctx->iaoq_n);
|
|
}
|
|
|
|
gen_set_label(taken);
|
|
|
|
/* Taken: Condition satisfied; nullify on forward branches. */
|
|
n = is_n && disp >= 0;
|
|
if (n && use_nullify_skip(ctx)) {
|
|
nullify_set(ctx, 0);
|
|
gen_goto_tb(ctx, 1, dest, dest + 4);
|
|
} else {
|
|
nullify_set(ctx, n);
|
|
gen_goto_tb(ctx, 1, ctx->iaoq_b, dest);
|
|
}
|
|
|
|
/* Not taken: the branch itself was nullified. */
|
|
if (ctx->null_lab) {
|
|
gen_set_label(ctx->null_lab);
|
|
ctx->null_lab = NULL;
|
|
return EXIT_IAQ_N_STALE;
|
|
} else {
|
|
return EXIT_GOTO_TB;
|
|
}
|
|
}
|
|
|
|
/* Emit an unconditional branch to an indirect target. This handles
|
|
nullification of the branch itself. */
|
|
static ExitStatus do_ibranch(DisasContext *ctx, TCGv dest,
|
|
unsigned link, bool is_n)
|
|
{
|
|
TCGv a0, a1, next, tmp;
|
|
TCGCond c;
|
|
|
|
assert(ctx->null_lab == NULL);
|
|
|
|
if (ctx->null_cond.c == TCG_COND_NEVER) {
|
|
if (link != 0) {
|
|
copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
|
|
}
|
|
next = get_temp(ctx);
|
|
tcg_gen_mov_tl(next, dest);
|
|
ctx->iaoq_n = -1;
|
|
ctx->iaoq_n_var = next;
|
|
if (is_n) {
|
|
ctx->null_cond.c = TCG_COND_ALWAYS;
|
|
}
|
|
} else if (is_n && use_nullify_skip(ctx)) {
|
|
/* The (conditional) branch, B, nullifies the next insn, N,
|
|
and we're allowed to skip execution N (no single-step or
|
|
tracepoint in effect). Since the goto_ptr that we must use
|
|
for the indirect branch consumes no special resources, we
|
|
can (conditionally) skip B and continue execution. */
|
|
/* The use_nullify_skip test implies we have a known control path. */
|
|
tcg_debug_assert(ctx->iaoq_b != -1);
|
|
tcg_debug_assert(ctx->iaoq_n != -1);
|
|
|
|
/* We do have to handle the non-local temporary, DEST, before
|
|
branching. Since IOAQ_F is not really live at this point, we
|
|
can simply store DEST optimistically. Similarly with IAOQ_B. */
|
|
tcg_gen_mov_tl(cpu_iaoq_f, dest);
|
|
tcg_gen_addi_tl(cpu_iaoq_b, dest, 4);
|
|
|
|
nullify_over(ctx);
|
|
if (link != 0) {
|
|
tcg_gen_movi_tl(cpu_gr[link], ctx->iaoq_n);
|
|
}
|
|
tcg_gen_lookup_and_goto_ptr(cpu_iaoq_f);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
} else {
|
|
cond_prep(&ctx->null_cond);
|
|
c = ctx->null_cond.c;
|
|
a0 = ctx->null_cond.a0;
|
|
a1 = ctx->null_cond.a1;
|
|
|
|
tmp = tcg_temp_new();
|
|
next = get_temp(ctx);
|
|
|
|
copy_iaoq_entry(tmp, ctx->iaoq_n, ctx->iaoq_n_var);
|
|
tcg_gen_movcond_tl(c, next, a0, a1, tmp, dest);
|
|
ctx->iaoq_n = -1;
|
|
ctx->iaoq_n_var = next;
|
|
|
|
if (link != 0) {
|
|
tcg_gen_movcond_tl(c, cpu_gr[link], a0, a1, cpu_gr[link], tmp);
|
|
}
|
|
|
|
if (is_n) {
|
|
/* The branch nullifies the next insn, which means the state of N
|
|
after the branch is the inverse of the state of N that applied
|
|
to the branch. */
|
|
tcg_gen_setcond_tl(tcg_invert_cond(c), cpu_psw_n, a0, a1);
|
|
cond_free(&ctx->null_cond);
|
|
ctx->null_cond = cond_make_n();
|
|
ctx->psw_n_nonzero = true;
|
|
} else {
|
|
cond_free(&ctx->null_cond);
|
|
}
|
|
}
|
|
|
|
return NO_EXIT;
|
|
}
|
|
|
|
/* On Linux, page zero is normally marked execute only + gateway.
|
|
Therefore normal read or write is supposed to fail, but specific
|
|
offsets have kernel code mapped to raise permissions to implement
|
|
system calls. Handling this via an explicit check here, rather
|
|
in than the "be disp(sr2,r0)" instruction that probably sent us
|
|
here, is the easiest way to handle the branch delay slot on the
|
|
aforementioned BE. */
|
|
static ExitStatus do_page_zero(DisasContext *ctx)
|
|
{
|
|
/* If by some means we get here with PSW[N]=1, that implies that
|
|
the B,GATE instruction would be skipped, and we'd fault on the
|
|
next insn within the privilaged page. */
|
|
switch (ctx->null_cond.c) {
|
|
case TCG_COND_NEVER:
|
|
break;
|
|
case TCG_COND_ALWAYS:
|
|
tcg_gen_movi_tl(cpu_psw_n, 0);
|
|
goto do_sigill;
|
|
default:
|
|
/* Since this is always the first (and only) insn within the
|
|
TB, we should know the state of PSW[N] from TB->FLAGS. */
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
/* Check that we didn't arrive here via some means that allowed
|
|
non-sequential instruction execution. Normally the PSW[B] bit
|
|
detects this by disallowing the B,GATE instruction to execute
|
|
under such conditions. */
|
|
if (ctx->iaoq_b != ctx->iaoq_f + 4) {
|
|
goto do_sigill;
|
|
}
|
|
|
|
switch (ctx->iaoq_f) {
|
|
case 0x00: /* Null pointer call */
|
|
gen_excp_1(EXCP_SIGSEGV);
|
|
return EXIT_NORETURN;
|
|
|
|
case 0xb0: /* LWS */
|
|
gen_excp_1(EXCP_SYSCALL_LWS);
|
|
return EXIT_NORETURN;
|
|
|
|
case 0xe0: /* SET_THREAD_POINTER */
|
|
tcg_gen_mov_tl(cpu_cr27, cpu_gr[26]);
|
|
tcg_gen_mov_tl(cpu_iaoq_f, cpu_gr[31]);
|
|
tcg_gen_addi_tl(cpu_iaoq_b, cpu_iaoq_f, 4);
|
|
return EXIT_IAQ_N_UPDATED;
|
|
|
|
case 0x100: /* SYSCALL */
|
|
gen_excp_1(EXCP_SYSCALL);
|
|
return EXIT_NORETURN;
|
|
|
|
default:
|
|
do_sigill:
|
|
gen_excp_1(EXCP_SIGILL);
|
|
return EXIT_NORETURN;
|
|
}
|
|
}
|
|
|
|
static ExitStatus trans_nop(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_break(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
nullify_over(ctx);
|
|
return nullify_end(ctx, gen_excp(ctx, EXCP_DEBUG));
|
|
}
|
|
|
|
static ExitStatus trans_sync(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
/* No point in nullifying the memory barrier. */
|
|
tcg_gen_mb(TCG_BAR_SC | TCG_MO_ALL);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_mfia(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tmp = dest_gpr(ctx, rt);
|
|
tcg_gen_movi_tl(tmp, ctx->iaoq_f);
|
|
save_gpr(ctx, rt, tmp);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_mfsp(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tmp = dest_gpr(ctx, rt);
|
|
|
|
/* ??? We don't implement space registers. */
|
|
tcg_gen_movi_tl(tmp, 0);
|
|
save_gpr(ctx, rt, tmp);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_mfctl(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned ctl = extract32(insn, 21, 5);
|
|
TCGv tmp;
|
|
|
|
switch (ctl) {
|
|
case 11: /* SAR */
|
|
#ifdef TARGET_HPPA64
|
|
if (extract32(insn, 14, 1) == 0) {
|
|
/* MFSAR without ,W masks low 5 bits. */
|
|
tmp = dest_gpr(ctx, rt);
|
|
tcg_gen_andi_tl(tmp, cpu_sar, 31);
|
|
save_gpr(ctx, rt, tmp);
|
|
break;
|
|
}
|
|
#endif
|
|
save_gpr(ctx, rt, cpu_sar);
|
|
break;
|
|
case 16: /* Interval Timer */
|
|
tmp = dest_gpr(ctx, rt);
|
|
tcg_gen_movi_tl(tmp, 0); /* FIXME */
|
|
save_gpr(ctx, rt, tmp);
|
|
break;
|
|
case 26:
|
|
save_gpr(ctx, rt, cpu_cr26);
|
|
break;
|
|
case 27:
|
|
save_gpr(ctx, rt, cpu_cr27);
|
|
break;
|
|
default:
|
|
/* All other control registers are privileged. */
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_mtctl(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rin = extract32(insn, 16, 5);
|
|
unsigned ctl = extract32(insn, 21, 5);
|
|
TCGv tmp;
|
|
|
|
if (ctl == 11) { /* SAR */
|
|
tmp = tcg_temp_new();
|
|
tcg_gen_andi_tl(tmp, load_gpr(ctx, rin), TARGET_LONG_BITS - 1);
|
|
save_or_nullify(ctx, cpu_sar, tmp);
|
|
tcg_temp_free(tmp);
|
|
} else {
|
|
/* All other control registers are privileged or read-only. */
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_mtsarcm(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rin = extract32(insn, 16, 5);
|
|
TCGv tmp = tcg_temp_new();
|
|
|
|
tcg_gen_not_tl(tmp, load_gpr(ctx, rin));
|
|
tcg_gen_andi_tl(tmp, tmp, TARGET_LONG_BITS - 1);
|
|
save_or_nullify(ctx, cpu_sar, tmp);
|
|
tcg_temp_free(tmp);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_ldsid(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv dest = dest_gpr(ctx, rt);
|
|
|
|
/* Since we don't implement space registers, this returns zero. */
|
|
tcg_gen_movi_tl(dest, 0);
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static const DisasInsn table_system[] = {
|
|
{ 0x00000000u, 0xfc001fe0u, trans_break },
|
|
/* We don't implement space register, so MTSP is a nop. */
|
|
{ 0x00001820u, 0xffe01fffu, trans_nop },
|
|
{ 0x00001840u, 0xfc00ffffu, trans_mtctl },
|
|
{ 0x016018c0u, 0xffe0ffffu, trans_mtsarcm },
|
|
{ 0x000014a0u, 0xffffffe0u, trans_mfia },
|
|
{ 0x000004a0u, 0xffff1fe0u, trans_mfsp },
|
|
{ 0x000008a0u, 0xfc1fffe0u, trans_mfctl },
|
|
{ 0x00000400u, 0xffffffffu, trans_sync },
|
|
{ 0x000010a0u, 0xfc1f3fe0u, trans_ldsid },
|
|
};
|
|
|
|
static ExitStatus trans_base_idx_mod(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
TCGv dest = dest_gpr(ctx, rb);
|
|
TCGv src1 = load_gpr(ctx, rb);
|
|
TCGv src2 = load_gpr(ctx, rx);
|
|
|
|
/* The only thing we need to do is the base register modification. */
|
|
tcg_gen_add_tl(dest, src1, src2);
|
|
save_gpr(ctx, rb, dest);
|
|
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_probe(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned is_write = extract32(insn, 6, 1);
|
|
TCGv dest;
|
|
|
|
nullify_over(ctx);
|
|
|
|
/* ??? Do something with priv level operand. */
|
|
dest = dest_gpr(ctx, rt);
|
|
if (is_write) {
|
|
gen_helper_probe_w(dest, load_gpr(ctx, rb));
|
|
} else {
|
|
gen_helper_probe_r(dest, load_gpr(ctx, rb));
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static const DisasInsn table_mem_mgmt[] = {
|
|
{ 0x04003280u, 0xfc003fffu, trans_nop }, /* fdc, disp */
|
|
{ 0x04001280u, 0xfc003fffu, trans_nop }, /* fdc, index */
|
|
{ 0x040012a0u, 0xfc003fffu, trans_base_idx_mod }, /* fdc, index, base mod */
|
|
{ 0x040012c0u, 0xfc003fffu, trans_nop }, /* fdce */
|
|
{ 0x040012e0u, 0xfc003fffu, trans_base_idx_mod }, /* fdce, base mod */
|
|
{ 0x04000280u, 0xfc001fffu, trans_nop }, /* fic 0a */
|
|
{ 0x040002a0u, 0xfc001fffu, trans_base_idx_mod }, /* fic 0a, base mod */
|
|
{ 0x040013c0u, 0xfc003fffu, trans_nop }, /* fic 4f */
|
|
{ 0x040013e0u, 0xfc003fffu, trans_base_idx_mod }, /* fic 4f, base mod */
|
|
{ 0x040002c0u, 0xfc001fffu, trans_nop }, /* fice */
|
|
{ 0x040002e0u, 0xfc001fffu, trans_base_idx_mod }, /* fice, base mod */
|
|
{ 0x04002700u, 0xfc003fffu, trans_nop }, /* pdc */
|
|
{ 0x04002720u, 0xfc003fffu, trans_base_idx_mod }, /* pdc, base mod */
|
|
{ 0x04001180u, 0xfc003fa0u, trans_probe }, /* probe */
|
|
{ 0x04003180u, 0xfc003fa0u, trans_probe }, /* probei */
|
|
};
|
|
|
|
static ExitStatus trans_add(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned ext = extract32(insn, 8, 4);
|
|
unsigned shift = extract32(insn, 6, 2);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tcg_r1, tcg_r2;
|
|
bool is_c = false;
|
|
bool is_l = false;
|
|
bool is_tc = false;
|
|
bool is_tsv = false;
|
|
ExitStatus ret;
|
|
|
|
switch (ext) {
|
|
case 0x6: /* ADD, SHLADD */
|
|
break;
|
|
case 0xa: /* ADD,L, SHLADD,L */
|
|
is_l = true;
|
|
break;
|
|
case 0xe: /* ADD,TSV, SHLADD,TSV (1) */
|
|
is_tsv = true;
|
|
break;
|
|
case 0x7: /* ADD,C */
|
|
is_c = true;
|
|
break;
|
|
case 0xf: /* ADD,C,TSV */
|
|
is_c = is_tsv = true;
|
|
break;
|
|
default:
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
tcg_r1 = load_gpr(ctx, r1);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_add(ctx, rt, tcg_r1, tcg_r2, shift, is_l, is_tsv, is_tc, is_c, cf);
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_sub(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned ext = extract32(insn, 6, 6);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tcg_r1, tcg_r2;
|
|
bool is_b = false;
|
|
bool is_tc = false;
|
|
bool is_tsv = false;
|
|
ExitStatus ret;
|
|
|
|
switch (ext) {
|
|
case 0x10: /* SUB */
|
|
break;
|
|
case 0x30: /* SUB,TSV */
|
|
is_tsv = true;
|
|
break;
|
|
case 0x14: /* SUB,B */
|
|
is_b = true;
|
|
break;
|
|
case 0x34: /* SUB,B,TSV */
|
|
is_b = is_tsv = true;
|
|
break;
|
|
case 0x13: /* SUB,TC */
|
|
is_tc = true;
|
|
break;
|
|
case 0x33: /* SUB,TSV,TC */
|
|
is_tc = is_tsv = true;
|
|
break;
|
|
default:
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
tcg_r1 = load_gpr(ctx, r1);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_sub(ctx, rt, tcg_r1, tcg_r2, is_tsv, is_b, is_tc, cf);
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_log(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tcg_r1, tcg_r2;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
tcg_r1 = load_gpr(ctx, r1);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_log(ctx, rt, tcg_r1, tcg_r2, cf, di->f.ttt);
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
/* OR r,0,t -> COPY (according to gas) */
|
|
static ExitStatus trans_copy(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
|
|
if (r1 == 0) {
|
|
TCGv dest = dest_gpr(ctx, rt);
|
|
tcg_gen_movi_tl(dest, 0);
|
|
save_gpr(ctx, rt, dest);
|
|
} else {
|
|
save_gpr(ctx, rt, cpu_gr[r1]);
|
|
}
|
|
cond_free(&ctx->null_cond);
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_cmpclr(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tcg_r1, tcg_r2;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
tcg_r1 = load_gpr(ctx, r1);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_cmpclr(ctx, rt, tcg_r1, tcg_r2, cf);
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_uxor(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tcg_r1, tcg_r2;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
tcg_r1 = load_gpr(ctx, r1);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_unit(ctx, rt, tcg_r1, tcg_r2, cf, false, tcg_gen_xor_tl);
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_uaddcm(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned is_tc = extract32(insn, 6, 1);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tcg_r1, tcg_r2, tmp;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
tcg_r1 = load_gpr(ctx, r1);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
tmp = get_temp(ctx);
|
|
tcg_gen_not_tl(tmp, tcg_r2);
|
|
ret = do_unit(ctx, rt, tcg_r1, tmp, cf, is_tc, tcg_gen_add_tl);
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_dcor(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned is_i = extract32(insn, 6, 1);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv tmp;
|
|
ExitStatus ret;
|
|
|
|
nullify_over(ctx);
|
|
|
|
tmp = get_temp(ctx);
|
|
tcg_gen_shri_tl(tmp, cpu_psw_cb, 3);
|
|
if (!is_i) {
|
|
tcg_gen_not_tl(tmp, tmp);
|
|
}
|
|
tcg_gen_andi_tl(tmp, tmp, 0x11111111);
|
|
tcg_gen_muli_tl(tmp, tmp, 6);
|
|
ret = do_unit(ctx, rt, tmp, load_gpr(ctx, r2), cf, false,
|
|
is_i ? tcg_gen_add_tl : tcg_gen_sub_tl);
|
|
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_ds(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
TCGv dest, add1, add2, addc, zero, in1, in2;
|
|
|
|
nullify_over(ctx);
|
|
|
|
in1 = load_gpr(ctx, r1);
|
|
in2 = load_gpr(ctx, r2);
|
|
|
|
add1 = tcg_temp_new();
|
|
add2 = tcg_temp_new();
|
|
addc = tcg_temp_new();
|
|
dest = tcg_temp_new();
|
|
zero = tcg_const_tl(0);
|
|
|
|
/* Form R1 << 1 | PSW[CB]{8}. */
|
|
tcg_gen_add_tl(add1, in1, in1);
|
|
tcg_gen_add_tl(add1, add1, cpu_psw_cb_msb);
|
|
|
|
/* Add or subtract R2, depending on PSW[V]. Proper computation of
|
|
carry{8} requires that we subtract via + ~R2 + 1, as described in
|
|
the manual. By extracting and masking V, we can produce the
|
|
proper inputs to the addition without movcond. */
|
|
tcg_gen_sari_tl(addc, cpu_psw_v, TARGET_LONG_BITS - 1);
|
|
tcg_gen_xor_tl(add2, in2, addc);
|
|
tcg_gen_andi_tl(addc, addc, 1);
|
|
/* ??? This is only correct for 32-bit. */
|
|
tcg_gen_add2_i32(dest, cpu_psw_cb_msb, add1, zero, add2, zero);
|
|
tcg_gen_add2_i32(dest, cpu_psw_cb_msb, dest, cpu_psw_cb_msb, addc, zero);
|
|
|
|
tcg_temp_free(addc);
|
|
tcg_temp_free(zero);
|
|
|
|
/* Write back the result register. */
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Write back PSW[CB]. */
|
|
tcg_gen_xor_tl(cpu_psw_cb, add1, add2);
|
|
tcg_gen_xor_tl(cpu_psw_cb, cpu_psw_cb, dest);
|
|
|
|
/* Write back PSW[V] for the division step. */
|
|
tcg_gen_neg_tl(cpu_psw_v, cpu_psw_cb_msb);
|
|
tcg_gen_xor_tl(cpu_psw_v, cpu_psw_v, in2);
|
|
|
|
/* Install the new nullification. */
|
|
if (cf) {
|
|
TCGv sv;
|
|
TCGV_UNUSED(sv);
|
|
if (cf >> 1 == 6) {
|
|
/* ??? The lshift is supposed to contribute to overflow. */
|
|
sv = do_add_sv(ctx, dest, add1, add2);
|
|
}
|
|
ctx->null_cond = do_cond(cf, dest, cpu_psw_cb_msb, sv);
|
|
}
|
|
|
|
tcg_temp_free(add1);
|
|
tcg_temp_free(add2);
|
|
tcg_temp_free(dest);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static const DisasInsn table_arith_log[] = {
|
|
{ 0x08000240u, 0xfc00ffffu, trans_nop }, /* or x,y,0 */
|
|
{ 0x08000240u, 0xffe0ffe0u, trans_copy }, /* or x,0,t */
|
|
{ 0x08000000u, 0xfc000fe0u, trans_log, .f.ttt = tcg_gen_andc_tl },
|
|
{ 0x08000200u, 0xfc000fe0u, trans_log, .f.ttt = tcg_gen_and_tl },
|
|
{ 0x08000240u, 0xfc000fe0u, trans_log, .f.ttt = tcg_gen_or_tl },
|
|
{ 0x08000280u, 0xfc000fe0u, trans_log, .f.ttt = tcg_gen_xor_tl },
|
|
{ 0x08000880u, 0xfc000fe0u, trans_cmpclr },
|
|
{ 0x08000380u, 0xfc000fe0u, trans_uxor },
|
|
{ 0x08000980u, 0xfc000fa0u, trans_uaddcm },
|
|
{ 0x08000b80u, 0xfc1f0fa0u, trans_dcor },
|
|
{ 0x08000440u, 0xfc000fe0u, trans_ds },
|
|
{ 0x08000700u, 0xfc0007e0u, trans_add }, /* add */
|
|
{ 0x08000400u, 0xfc0006e0u, trans_sub }, /* sub; sub,b; sub,tsv */
|
|
{ 0x080004c0u, 0xfc0007e0u, trans_sub }, /* sub,tc; sub,tsv,tc */
|
|
{ 0x08000200u, 0xfc000320u, trans_add }, /* shladd */
|
|
};
|
|
|
|
static ExitStatus trans_addi(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
target_long im = low_sextract(insn, 0, 11);
|
|
unsigned e1 = extract32(insn, 11, 1);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned o1 = extract32(insn, 26, 1);
|
|
TCGv tcg_im, tcg_r2;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
tcg_im = load_const(ctx, im);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_add(ctx, rt, tcg_im, tcg_r2, 0, false, e1, !o1, false, cf);
|
|
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_subi(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
target_long im = low_sextract(insn, 0, 11);
|
|
unsigned e1 = extract32(insn, 11, 1);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
TCGv tcg_im, tcg_r2;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
tcg_im = load_const(ctx, im);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_sub(ctx, rt, tcg_im, tcg_r2, e1, false, false, cf);
|
|
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_cmpiclr(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
target_long im = low_sextract(insn, 0, 11);
|
|
unsigned cf = extract32(insn, 12, 4);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
TCGv tcg_im, tcg_r2;
|
|
ExitStatus ret;
|
|
|
|
if (cf) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
tcg_im = load_const(ctx, im);
|
|
tcg_r2 = load_gpr(ctx, r2);
|
|
ret = do_cmpclr(ctx, rt, tcg_im, tcg_r2, cf);
|
|
|
|
return nullify_end(ctx, ret);
|
|
}
|
|
|
|
static ExitStatus trans_ld_idx_i(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned sz = extract32(insn, 6, 2);
|
|
unsigned a = extract32(insn, 13, 1);
|
|
int disp = low_sextract(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
int modify = (m ? (a ? -1 : 1) : 0);
|
|
TCGMemOp mop = MO_TE | sz;
|
|
|
|
return do_load(ctx, rt, rb, 0, 0, disp, modify, mop);
|
|
}
|
|
|
|
static ExitStatus trans_ld_idx_x(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned sz = extract32(insn, 6, 2);
|
|
unsigned u = extract32(insn, 13, 1);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
TCGMemOp mop = MO_TE | sz;
|
|
|
|
return do_load(ctx, rt, rb, rx, u ? sz : 0, 0, m, mop);
|
|
}
|
|
|
|
static ExitStatus trans_st_idx_i(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
int disp = low_sextract(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned sz = extract32(insn, 6, 2);
|
|
unsigned a = extract32(insn, 13, 1);
|
|
unsigned rr = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
int modify = (m ? (a ? -1 : 1) : 0);
|
|
TCGMemOp mop = MO_TE | sz;
|
|
|
|
return do_store(ctx, rr, rb, disp, modify, mop);
|
|
}
|
|
|
|
static ExitStatus trans_ldcw(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned i = extract32(insn, 12, 1);
|
|
unsigned au = extract32(insn, 13, 1);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
TCGMemOp mop = MO_TEUL | MO_ALIGN_16;
|
|
TCGv zero, addr, base, dest;
|
|
int modify, disp = 0, scale = 0;
|
|
|
|
nullify_over(ctx);
|
|
|
|
/* ??? Share more code with do_load and do_load_{32,64}. */
|
|
|
|
if (i) {
|
|
modify = (m ? (au ? -1 : 1) : 0);
|
|
disp = low_sextract(rx, 0, 5);
|
|
rx = 0;
|
|
} else {
|
|
modify = m;
|
|
if (au) {
|
|
scale = mop & MO_SIZE;
|
|
}
|
|
}
|
|
if (modify) {
|
|
/* Base register modification. Make sure if RT == RB, we see
|
|
the result of the load. */
|
|
dest = get_temp(ctx);
|
|
} else {
|
|
dest = dest_gpr(ctx, rt);
|
|
}
|
|
|
|
addr = tcg_temp_new();
|
|
base = load_gpr(ctx, rb);
|
|
if (rx) {
|
|
tcg_gen_shli_tl(addr, cpu_gr[rx], scale);
|
|
tcg_gen_add_tl(addr, addr, base);
|
|
} else {
|
|
tcg_gen_addi_tl(addr, base, disp);
|
|
}
|
|
|
|
zero = tcg_const_tl(0);
|
|
tcg_gen_atomic_xchg_tl(dest, (modify <= 0 ? addr : base),
|
|
zero, MMU_USER_IDX, mop);
|
|
if (modify) {
|
|
save_gpr(ctx, rb, addr);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_stby(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
target_long disp = low_sextract(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned a = extract32(insn, 13, 1);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
TCGv addr, val;
|
|
|
|
nullify_over(ctx);
|
|
|
|
addr = tcg_temp_new();
|
|
if (m || disp == 0) {
|
|
tcg_gen_mov_tl(addr, load_gpr(ctx, rb));
|
|
} else {
|
|
tcg_gen_addi_tl(addr, load_gpr(ctx, rb), disp);
|
|
}
|
|
val = load_gpr(ctx, rt);
|
|
|
|
if (a) {
|
|
gen_helper_stby_e(cpu_env, addr, val);
|
|
} else {
|
|
gen_helper_stby_b(cpu_env, addr, val);
|
|
}
|
|
|
|
if (m) {
|
|
tcg_gen_addi_tl(addr, addr, disp);
|
|
tcg_gen_andi_tl(addr, addr, ~3);
|
|
save_gpr(ctx, rb, addr);
|
|
}
|
|
tcg_temp_free(addr);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static const DisasInsn table_index_mem[] = {
|
|
{ 0x0c001000u, 0xfc001300, trans_ld_idx_i }, /* LD[BHWD], im */
|
|
{ 0x0c000000u, 0xfc001300, trans_ld_idx_x }, /* LD[BHWD], rx */
|
|
{ 0x0c001200u, 0xfc001300, trans_st_idx_i }, /* ST[BHWD] */
|
|
{ 0x0c0001c0u, 0xfc0003c0, trans_ldcw },
|
|
{ 0x0c001300u, 0xfc0013c0, trans_stby },
|
|
};
|
|
|
|
static ExitStatus trans_ldil(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned rt = extract32(insn, 21, 5);
|
|
target_long i = assemble_21(insn);
|
|
TCGv tcg_rt = dest_gpr(ctx, rt);
|
|
|
|
tcg_gen_movi_tl(tcg_rt, i);
|
|
save_gpr(ctx, rt, tcg_rt);
|
|
cond_free(&ctx->null_cond);
|
|
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_addil(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned rt = extract32(insn, 21, 5);
|
|
target_long i = assemble_21(insn);
|
|
TCGv tcg_rt = load_gpr(ctx, rt);
|
|
TCGv tcg_r1 = dest_gpr(ctx, 1);
|
|
|
|
tcg_gen_addi_tl(tcg_r1, tcg_rt, i);
|
|
save_gpr(ctx, 1, tcg_r1);
|
|
cond_free(&ctx->null_cond);
|
|
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_ldo(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
target_long i = assemble_16(insn);
|
|
TCGv tcg_rt = dest_gpr(ctx, rt);
|
|
|
|
/* Special case rb == 0, for the LDI pseudo-op.
|
|
The COPY pseudo-op is handled for free within tcg_gen_addi_tl. */
|
|
if (rb == 0) {
|
|
tcg_gen_movi_tl(tcg_rt, i);
|
|
} else {
|
|
tcg_gen_addi_tl(tcg_rt, cpu_gr[rb], i);
|
|
}
|
|
save_gpr(ctx, rt, tcg_rt);
|
|
cond_free(&ctx->null_cond);
|
|
|
|
return NO_EXIT;
|
|
}
|
|
|
|
static ExitStatus trans_load(DisasContext *ctx, uint32_t insn,
|
|
bool is_mod, TCGMemOp mop)
|
|
{
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
target_long i = assemble_16(insn);
|
|
|
|
return do_load(ctx, rt, rb, 0, 0, i, is_mod ? (i < 0 ? -1 : 1) : 0, mop);
|
|
}
|
|
|
|
static ExitStatus trans_load_w(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
target_long i = assemble_16a(insn);
|
|
unsigned ext2 = extract32(insn, 1, 2);
|
|
|
|
switch (ext2) {
|
|
case 0:
|
|
case 1:
|
|
/* FLDW without modification. */
|
|
return do_floadw(ctx, ext2 * 32 + rt, rb, 0, 0, i, 0);
|
|
case 2:
|
|
/* LDW with modification. Note that the sign of I selects
|
|
post-dec vs pre-inc. */
|
|
return do_load(ctx, rt, rb, 0, 0, i, (i < 0 ? 1 : -1), MO_TEUL);
|
|
default:
|
|
return gen_illegal(ctx);
|
|
}
|
|
}
|
|
|
|
static ExitStatus trans_fload_mod(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
target_long i = assemble_16a(insn);
|
|
unsigned t1 = extract32(insn, 1, 1);
|
|
unsigned a = extract32(insn, 2, 1);
|
|
unsigned t0 = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
|
|
/* FLDW with modification. */
|
|
return do_floadw(ctx, t1 * 32 + t0, rb, 0, 0, i, (a ? -1 : 1));
|
|
}
|
|
|
|
static ExitStatus trans_store(DisasContext *ctx, uint32_t insn,
|
|
bool is_mod, TCGMemOp mop)
|
|
{
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
target_long i = assemble_16(insn);
|
|
|
|
return do_store(ctx, rt, rb, i, is_mod ? (i < 0 ? -1 : 1) : 0, mop);
|
|
}
|
|
|
|
static ExitStatus trans_store_w(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
target_long i = assemble_16a(insn);
|
|
unsigned ext2 = extract32(insn, 1, 2);
|
|
|
|
switch (ext2) {
|
|
case 0:
|
|
case 1:
|
|
/* FSTW without modification. */
|
|
return do_fstorew(ctx, ext2 * 32 + rt, rb, 0, 0, i, 0);
|
|
case 2:
|
|
/* LDW with modification. */
|
|
return do_store(ctx, rt, rb, i, (i < 0 ? 1 : -1), MO_TEUL);
|
|
default:
|
|
return gen_illegal(ctx);
|
|
}
|
|
}
|
|
|
|
static ExitStatus trans_fstore_mod(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
target_long i = assemble_16a(insn);
|
|
unsigned t1 = extract32(insn, 1, 1);
|
|
unsigned a = extract32(insn, 2, 1);
|
|
unsigned t0 = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
|
|
/* FSTW with modification. */
|
|
return do_fstorew(ctx, t1 * 32 + t0, rb, 0, 0, i, (a ? -1 : 1));
|
|
}
|
|
|
|
static ExitStatus trans_copr_w(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned t0 = extract32(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned t1 = extract32(insn, 6, 1);
|
|
unsigned ext3 = extract32(insn, 7, 3);
|
|
/* unsigned cc = extract32(insn, 10, 2); */
|
|
unsigned i = extract32(insn, 12, 1);
|
|
unsigned ua = extract32(insn, 13, 1);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned rt = t1 * 32 + t0;
|
|
int modify = (m ? (ua ? -1 : 1) : 0);
|
|
int disp, scale;
|
|
|
|
if (i == 0) {
|
|
scale = (ua ? 2 : 0);
|
|
disp = 0;
|
|
modify = m;
|
|
} else {
|
|
disp = low_sextract(rx, 0, 5);
|
|
scale = 0;
|
|
rx = 0;
|
|
modify = (m ? (ua ? -1 : 1) : 0);
|
|
}
|
|
|
|
switch (ext3) {
|
|
case 0: /* FLDW */
|
|
return do_floadw(ctx, rt, rb, rx, scale, disp, modify);
|
|
case 4: /* FSTW */
|
|
return do_fstorew(ctx, rt, rb, rx, scale, disp, modify);
|
|
}
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
static ExitStatus trans_copr_dw(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned m = extract32(insn, 5, 1);
|
|
unsigned ext4 = extract32(insn, 6, 4);
|
|
/* unsigned cc = extract32(insn, 10, 2); */
|
|
unsigned i = extract32(insn, 12, 1);
|
|
unsigned ua = extract32(insn, 13, 1);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
int modify = (m ? (ua ? -1 : 1) : 0);
|
|
int disp, scale;
|
|
|
|
if (i == 0) {
|
|
scale = (ua ? 3 : 0);
|
|
disp = 0;
|
|
modify = m;
|
|
} else {
|
|
disp = low_sextract(rx, 0, 5);
|
|
scale = 0;
|
|
rx = 0;
|
|
modify = (m ? (ua ? -1 : 1) : 0);
|
|
}
|
|
|
|
switch (ext4) {
|
|
case 0: /* FLDD */
|
|
return do_floadd(ctx, rt, rb, rx, scale, disp, modify);
|
|
case 8: /* FSTD */
|
|
return do_fstored(ctx, rt, rb, rx, scale, disp, modify);
|
|
default:
|
|
return gen_illegal(ctx);
|
|
}
|
|
}
|
|
|
|
static ExitStatus trans_cmpb(DisasContext *ctx, uint32_t insn,
|
|
bool is_true, bool is_imm, bool is_dw)
|
|
{
|
|
target_long disp = assemble_12(insn) * 4;
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned r = extract32(insn, 21, 5);
|
|
unsigned cf = c * 2 + !is_true;
|
|
TCGv dest, in1, in2, sv;
|
|
DisasCond cond;
|
|
|
|
nullify_over(ctx);
|
|
|
|
if (is_imm) {
|
|
in1 = load_const(ctx, low_sextract(insn, 16, 5));
|
|
} else {
|
|
in1 = load_gpr(ctx, extract32(insn, 16, 5));
|
|
}
|
|
in2 = load_gpr(ctx, r);
|
|
dest = get_temp(ctx);
|
|
|
|
tcg_gen_sub_tl(dest, in1, in2);
|
|
|
|
TCGV_UNUSED(sv);
|
|
if (c == 6) {
|
|
sv = do_sub_sv(ctx, dest, in1, in2);
|
|
}
|
|
|
|
cond = do_sub_cond(cf, dest, in1, in2, sv);
|
|
return do_cbranch(ctx, disp, n, &cond);
|
|
}
|
|
|
|
static ExitStatus trans_addb(DisasContext *ctx, uint32_t insn,
|
|
bool is_true, bool is_imm)
|
|
{
|
|
target_long disp = assemble_12(insn) * 4;
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned r = extract32(insn, 21, 5);
|
|
unsigned cf = c * 2 + !is_true;
|
|
TCGv dest, in1, in2, sv, cb_msb;
|
|
DisasCond cond;
|
|
|
|
nullify_over(ctx);
|
|
|
|
if (is_imm) {
|
|
in1 = load_const(ctx, low_sextract(insn, 16, 5));
|
|
} else {
|
|
in1 = load_gpr(ctx, extract32(insn, 16, 5));
|
|
}
|
|
in2 = load_gpr(ctx, r);
|
|
dest = dest_gpr(ctx, r);
|
|
TCGV_UNUSED(sv);
|
|
TCGV_UNUSED(cb_msb);
|
|
|
|
switch (c) {
|
|
default:
|
|
tcg_gen_add_tl(dest, in1, in2);
|
|
break;
|
|
case 4: case 5:
|
|
cb_msb = get_temp(ctx);
|
|
tcg_gen_movi_tl(cb_msb, 0);
|
|
tcg_gen_add2_tl(dest, cb_msb, in1, cb_msb, in2, cb_msb);
|
|
break;
|
|
case 6:
|
|
tcg_gen_add_tl(dest, in1, in2);
|
|
sv = do_add_sv(ctx, dest, in1, in2);
|
|
break;
|
|
}
|
|
|
|
cond = do_cond(cf, dest, cb_msb, sv);
|
|
return do_cbranch(ctx, disp, n, &cond);
|
|
}
|
|
|
|
static ExitStatus trans_bb(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
target_long disp = assemble_12(insn) * 4;
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned c = extract32(insn, 15, 1);
|
|
unsigned r = extract32(insn, 16, 5);
|
|
unsigned p = extract32(insn, 21, 5);
|
|
unsigned i = extract32(insn, 26, 1);
|
|
TCGv tmp, tcg_r;
|
|
DisasCond cond;
|
|
|
|
nullify_over(ctx);
|
|
|
|
tmp = tcg_temp_new();
|
|
tcg_r = load_gpr(ctx, r);
|
|
if (i) {
|
|
tcg_gen_shli_tl(tmp, tcg_r, p);
|
|
} else {
|
|
tcg_gen_shl_tl(tmp, tcg_r, cpu_sar);
|
|
}
|
|
|
|
cond = cond_make_0(c ? TCG_COND_GE : TCG_COND_LT, tmp);
|
|
tcg_temp_free(tmp);
|
|
return do_cbranch(ctx, disp, n, &cond);
|
|
}
|
|
|
|
static ExitStatus trans_movb(DisasContext *ctx, uint32_t insn, bool is_imm)
|
|
{
|
|
target_long disp = assemble_12(insn) * 4;
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned t = extract32(insn, 16, 5);
|
|
unsigned r = extract32(insn, 21, 5);
|
|
TCGv dest;
|
|
DisasCond cond;
|
|
|
|
nullify_over(ctx);
|
|
|
|
dest = dest_gpr(ctx, r);
|
|
if (is_imm) {
|
|
tcg_gen_movi_tl(dest, low_sextract(t, 0, 5));
|
|
} else if (t == 0) {
|
|
tcg_gen_movi_tl(dest, 0);
|
|
} else {
|
|
tcg_gen_mov_tl(dest, cpu_gr[t]);
|
|
}
|
|
|
|
cond = do_sed_cond(c, dest);
|
|
return do_cbranch(ctx, disp, n, &cond);
|
|
}
|
|
|
|
static ExitStatus trans_shrpw_sar(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
TCGv dest;
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
dest = dest_gpr(ctx, rt);
|
|
if (r1 == 0) {
|
|
tcg_gen_ext32u_tl(dest, load_gpr(ctx, r2));
|
|
tcg_gen_shr_tl(dest, dest, cpu_sar);
|
|
} else if (r1 == r2) {
|
|
TCGv_i32 t32 = tcg_temp_new_i32();
|
|
tcg_gen_trunc_tl_i32(t32, load_gpr(ctx, r2));
|
|
tcg_gen_rotr_i32(t32, t32, cpu_sar);
|
|
tcg_gen_extu_i32_tl(dest, t32);
|
|
tcg_temp_free_i32(t32);
|
|
} else {
|
|
TCGv_i64 t = tcg_temp_new_i64();
|
|
TCGv_i64 s = tcg_temp_new_i64();
|
|
|
|
tcg_gen_concat_tl_i64(t, load_gpr(ctx, r2), load_gpr(ctx, r1));
|
|
tcg_gen_extu_tl_i64(s, cpu_sar);
|
|
tcg_gen_shr_i64(t, t, s);
|
|
tcg_gen_trunc_i64_tl(dest, t);
|
|
|
|
tcg_temp_free_i64(t);
|
|
tcg_temp_free_i64(s);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_shrpw_imm(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned cpos = extract32(insn, 5, 5);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned r1 = extract32(insn, 16, 5);
|
|
unsigned r2 = extract32(insn, 21, 5);
|
|
unsigned sa = 31 - cpos;
|
|
TCGv dest, t2;
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
dest = dest_gpr(ctx, rt);
|
|
t2 = load_gpr(ctx, r2);
|
|
if (r1 == r2) {
|
|
TCGv_i32 t32 = tcg_temp_new_i32();
|
|
tcg_gen_trunc_tl_i32(t32, t2);
|
|
tcg_gen_rotri_i32(t32, t32, sa);
|
|
tcg_gen_extu_i32_tl(dest, t32);
|
|
tcg_temp_free_i32(t32);
|
|
} else if (r1 == 0) {
|
|
tcg_gen_extract_tl(dest, t2, sa, 32 - sa);
|
|
} else {
|
|
TCGv t0 = tcg_temp_new();
|
|
tcg_gen_extract_tl(t0, t2, sa, 32 - sa);
|
|
tcg_gen_deposit_tl(dest, t0, cpu_gr[r1], 32 - sa, sa);
|
|
tcg_temp_free(t0);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_extrw_sar(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned clen = extract32(insn, 0, 5);
|
|
unsigned is_se = extract32(insn, 10, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
unsigned rr = extract32(insn, 21, 5);
|
|
unsigned len = 32 - clen;
|
|
TCGv dest, src, tmp;
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
dest = dest_gpr(ctx, rt);
|
|
src = load_gpr(ctx, rr);
|
|
tmp = tcg_temp_new();
|
|
|
|
/* Recall that SAR is using big-endian bit numbering. */
|
|
tcg_gen_xori_tl(tmp, cpu_sar, TARGET_LONG_BITS - 1);
|
|
if (is_se) {
|
|
tcg_gen_sar_tl(dest, src, tmp);
|
|
tcg_gen_sextract_tl(dest, dest, 0, len);
|
|
} else {
|
|
tcg_gen_shr_tl(dest, src, tmp);
|
|
tcg_gen_extract_tl(dest, dest, 0, len);
|
|
}
|
|
tcg_temp_free(tmp);
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_extrw_imm(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned clen = extract32(insn, 0, 5);
|
|
unsigned pos = extract32(insn, 5, 5);
|
|
unsigned is_se = extract32(insn, 10, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned rt = extract32(insn, 16, 5);
|
|
unsigned rr = extract32(insn, 21, 5);
|
|
unsigned len = 32 - clen;
|
|
unsigned cpos = 31 - pos;
|
|
TCGv dest, src;
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
dest = dest_gpr(ctx, rt);
|
|
src = load_gpr(ctx, rr);
|
|
if (is_se) {
|
|
tcg_gen_sextract_tl(dest, src, cpos, len);
|
|
} else {
|
|
tcg_gen_extract_tl(dest, src, cpos, len);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static const DisasInsn table_sh_ex[] = {
|
|
{ 0xd0000000u, 0xfc001fe0u, trans_shrpw_sar },
|
|
{ 0xd0000800u, 0xfc001c00u, trans_shrpw_imm },
|
|
{ 0xd0001000u, 0xfc001be0u, trans_extrw_sar },
|
|
{ 0xd0001800u, 0xfc001800u, trans_extrw_imm },
|
|
};
|
|
|
|
static ExitStatus trans_depw_imm_c(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned clen = extract32(insn, 0, 5);
|
|
unsigned cpos = extract32(insn, 5, 5);
|
|
unsigned nz = extract32(insn, 10, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
target_long val = low_sextract(insn, 16, 5);
|
|
unsigned rt = extract32(insn, 21, 5);
|
|
unsigned len = 32 - clen;
|
|
target_long mask0, mask1;
|
|
TCGv dest;
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
if (cpos + len > 32) {
|
|
len = 32 - cpos;
|
|
}
|
|
|
|
dest = dest_gpr(ctx, rt);
|
|
mask0 = deposit64(0, cpos, len, val);
|
|
mask1 = deposit64(-1, cpos, len, val);
|
|
|
|
if (nz) {
|
|
TCGv src = load_gpr(ctx, rt);
|
|
if (mask1 != -1) {
|
|
tcg_gen_andi_tl(dest, src, mask1);
|
|
src = dest;
|
|
}
|
|
tcg_gen_ori_tl(dest, src, mask0);
|
|
} else {
|
|
tcg_gen_movi_tl(dest, mask0);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_depw_imm(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned clen = extract32(insn, 0, 5);
|
|
unsigned cpos = extract32(insn, 5, 5);
|
|
unsigned nz = extract32(insn, 10, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned rr = extract32(insn, 16, 5);
|
|
unsigned rt = extract32(insn, 21, 5);
|
|
unsigned rs = nz ? rt : 0;
|
|
unsigned len = 32 - clen;
|
|
TCGv dest, val;
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
if (cpos + len > 32) {
|
|
len = 32 - cpos;
|
|
}
|
|
|
|
dest = dest_gpr(ctx, rt);
|
|
val = load_gpr(ctx, rr);
|
|
if (rs == 0) {
|
|
tcg_gen_deposit_z_tl(dest, val, cpos, len);
|
|
} else {
|
|
tcg_gen_deposit_tl(dest, cpu_gr[rs], val, cpos, len);
|
|
}
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_depw_sar(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned clen = extract32(insn, 0, 5);
|
|
unsigned nz = extract32(insn, 10, 1);
|
|
unsigned i = extract32(insn, 12, 1);
|
|
unsigned c = extract32(insn, 13, 3);
|
|
unsigned rt = extract32(insn, 21, 5);
|
|
unsigned rs = nz ? rt : 0;
|
|
unsigned len = 32 - clen;
|
|
TCGv val, mask, tmp, shift, dest;
|
|
unsigned msb = 1U << (len - 1);
|
|
|
|
if (c) {
|
|
nullify_over(ctx);
|
|
}
|
|
|
|
if (i) {
|
|
val = load_const(ctx, low_sextract(insn, 16, 5));
|
|
} else {
|
|
val = load_gpr(ctx, extract32(insn, 16, 5));
|
|
}
|
|
dest = dest_gpr(ctx, rt);
|
|
shift = tcg_temp_new();
|
|
tmp = tcg_temp_new();
|
|
|
|
/* Convert big-endian bit numbering in SAR to left-shift. */
|
|
tcg_gen_xori_tl(shift, cpu_sar, TARGET_LONG_BITS - 1);
|
|
|
|
mask = tcg_const_tl(msb + (msb - 1));
|
|
tcg_gen_and_tl(tmp, val, mask);
|
|
if (rs) {
|
|
tcg_gen_shl_tl(mask, mask, shift);
|
|
tcg_gen_shl_tl(tmp, tmp, shift);
|
|
tcg_gen_andc_tl(dest, cpu_gr[rs], mask);
|
|
tcg_gen_or_tl(dest, dest, tmp);
|
|
} else {
|
|
tcg_gen_shl_tl(dest, tmp, shift);
|
|
}
|
|
tcg_temp_free(shift);
|
|
tcg_temp_free(mask);
|
|
tcg_temp_free(tmp);
|
|
save_gpr(ctx, rt, dest);
|
|
|
|
/* Install the new nullification. */
|
|
cond_free(&ctx->null_cond);
|
|
if (c) {
|
|
ctx->null_cond = do_sed_cond(c, dest);
|
|
}
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static const DisasInsn table_depw[] = {
|
|
{ 0xd4000000u, 0xfc000be0u, trans_depw_sar },
|
|
{ 0xd4000800u, 0xfc001800u, trans_depw_imm },
|
|
{ 0xd4001800u, 0xfc001800u, trans_depw_imm_c },
|
|
};
|
|
|
|
static ExitStatus trans_be(DisasContext *ctx, uint32_t insn, bool is_l)
|
|
{
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned b = extract32(insn, 21, 5);
|
|
target_long disp = assemble_17(insn);
|
|
|
|
/* unsigned s = low_uextract(insn, 13, 3); */
|
|
/* ??? It seems like there should be a good way of using
|
|
"be disp(sr2, r0)", the canonical gateway entry mechanism
|
|
to our advantage. But that appears to be inconvenient to
|
|
manage along side branch delay slots. Therefore we handle
|
|
entry into the gateway page via absolute address. */
|
|
|
|
/* Since we don't implement spaces, just branch. Do notice the special
|
|
case of "be disp(*,r0)" using a direct branch to disp, so that we can
|
|
goto_tb to the TB containing the syscall. */
|
|
if (b == 0) {
|
|
return do_dbranch(ctx, disp, is_l ? 31 : 0, n);
|
|
} else {
|
|
TCGv tmp = get_temp(ctx);
|
|
tcg_gen_addi_tl(tmp, load_gpr(ctx, b), disp);
|
|
return do_ibranch(ctx, tmp, is_l ? 31 : 0, n);
|
|
}
|
|
}
|
|
|
|
static ExitStatus trans_bl(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned link = extract32(insn, 21, 5);
|
|
target_long disp = assemble_17(insn);
|
|
|
|
return do_dbranch(ctx, iaoq_dest(ctx, disp), link, n);
|
|
}
|
|
|
|
static ExitStatus trans_bl_long(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned n = extract32(insn, 1, 1);
|
|
target_long disp = assemble_22(insn);
|
|
|
|
return do_dbranch(ctx, iaoq_dest(ctx, disp), 2, n);
|
|
}
|
|
|
|
static ExitStatus trans_blr(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
unsigned link = extract32(insn, 21, 5);
|
|
TCGv tmp = get_temp(ctx);
|
|
|
|
tcg_gen_shli_tl(tmp, load_gpr(ctx, rx), 3);
|
|
tcg_gen_addi_tl(tmp, tmp, ctx->iaoq_f + 8);
|
|
return do_ibranch(ctx, tmp, link, n);
|
|
}
|
|
|
|
static ExitStatus trans_bv(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned rx = extract32(insn, 16, 5);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
TCGv dest;
|
|
|
|
if (rx == 0) {
|
|
dest = load_gpr(ctx, rb);
|
|
} else {
|
|
dest = get_temp(ctx);
|
|
tcg_gen_shli_tl(dest, load_gpr(ctx, rx), 3);
|
|
tcg_gen_add_tl(dest, dest, load_gpr(ctx, rb));
|
|
}
|
|
return do_ibranch(ctx, dest, 0, n);
|
|
}
|
|
|
|
static ExitStatus trans_bve(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned n = extract32(insn, 1, 1);
|
|
unsigned rb = extract32(insn, 21, 5);
|
|
unsigned link = extract32(insn, 13, 1) ? 2 : 0;
|
|
|
|
return do_ibranch(ctx, load_gpr(ctx, rb), link, n);
|
|
}
|
|
|
|
static const DisasInsn table_branch[] = {
|
|
{ 0xe8000000u, 0xfc006000u, trans_bl }, /* B,L and B,L,PUSH */
|
|
{ 0xe800a000u, 0xfc00e000u, trans_bl_long },
|
|
{ 0xe8004000u, 0xfc00fffdu, trans_blr },
|
|
{ 0xe800c000u, 0xfc00fffdu, trans_bv },
|
|
{ 0xe800d000u, 0xfc00dffcu, trans_bve },
|
|
};
|
|
|
|
static ExitStatus trans_fop_wew_0c(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_wew(ctx, rt, ra, di->f.wew);
|
|
}
|
|
|
|
static ExitStatus trans_fop_wew_0e(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = assemble_rt64(insn);
|
|
unsigned ra = assemble_ra64(insn);
|
|
return do_fop_wew(ctx, rt, ra, di->f.wew);
|
|
}
|
|
|
|
static ExitStatus trans_fop_ded(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_ded(ctx, rt, ra, di->f.ded);
|
|
}
|
|
|
|
static ExitStatus trans_fop_wed_0c(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_wed(ctx, rt, ra, di->f.wed);
|
|
}
|
|
|
|
static ExitStatus trans_fop_wed_0e(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = assemble_rt64(insn);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_wed(ctx, rt, ra, di->f.wed);
|
|
}
|
|
|
|
static ExitStatus trans_fop_dew_0c(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_dew(ctx, rt, ra, di->f.dew);
|
|
}
|
|
|
|
static ExitStatus trans_fop_dew_0e(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned ra = assemble_ra64(insn);
|
|
return do_fop_dew(ctx, rt, ra, di->f.dew);
|
|
}
|
|
|
|
static ExitStatus trans_fop_weww_0c(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned rb = extract32(insn, 16, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_weww(ctx, rt, ra, rb, di->f.weww);
|
|
}
|
|
|
|
static ExitStatus trans_fop_weww_0e(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = assemble_rt64(insn);
|
|
unsigned rb = assemble_rb64(insn);
|
|
unsigned ra = assemble_ra64(insn);
|
|
return do_fop_weww(ctx, rt, ra, rb, di->f.weww);
|
|
}
|
|
|
|
static ExitStatus trans_fop_dedd(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned rb = extract32(insn, 16, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fop_dedd(ctx, rt, ra, rb, di->f.dedd);
|
|
}
|
|
|
|
static void gen_fcpy_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
|
|
{
|
|
tcg_gen_mov_i32(dst, src);
|
|
}
|
|
|
|
static void gen_fcpy_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
|
|
{
|
|
tcg_gen_mov_i64(dst, src);
|
|
}
|
|
|
|
static void gen_fabs_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
|
|
{
|
|
tcg_gen_andi_i32(dst, src, INT32_MAX);
|
|
}
|
|
|
|
static void gen_fabs_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
|
|
{
|
|
tcg_gen_andi_i64(dst, src, INT64_MAX);
|
|
}
|
|
|
|
static void gen_fneg_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
|
|
{
|
|
tcg_gen_xori_i32(dst, src, INT32_MIN);
|
|
}
|
|
|
|
static void gen_fneg_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
|
|
{
|
|
tcg_gen_xori_i64(dst, src, INT64_MIN);
|
|
}
|
|
|
|
static void gen_fnegabs_s(TCGv_i32 dst, TCGv_env unused, TCGv_i32 src)
|
|
{
|
|
tcg_gen_ori_i32(dst, src, INT32_MIN);
|
|
}
|
|
|
|
static void gen_fnegabs_d(TCGv_i64 dst, TCGv_env unused, TCGv_i64 src)
|
|
{
|
|
tcg_gen_ori_i64(dst, src, INT64_MIN);
|
|
}
|
|
|
|
static ExitStatus do_fcmp_s(DisasContext *ctx, unsigned ra, unsigned rb,
|
|
unsigned y, unsigned c)
|
|
{
|
|
TCGv_i32 ta, tb, tc, ty;
|
|
|
|
nullify_over(ctx);
|
|
|
|
ta = load_frw0_i32(ra);
|
|
tb = load_frw0_i32(rb);
|
|
ty = tcg_const_i32(y);
|
|
tc = tcg_const_i32(c);
|
|
|
|
gen_helper_fcmp_s(cpu_env, ta, tb, ty, tc);
|
|
|
|
tcg_temp_free_i32(ta);
|
|
tcg_temp_free_i32(tb);
|
|
tcg_temp_free_i32(ty);
|
|
tcg_temp_free_i32(tc);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_fcmp_s_0c(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned c = extract32(insn, 0, 5);
|
|
unsigned y = extract32(insn, 13, 3);
|
|
unsigned rb = extract32(insn, 16, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
return do_fcmp_s(ctx, ra, rb, y, c);
|
|
}
|
|
|
|
static ExitStatus trans_fcmp_s_0e(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned c = extract32(insn, 0, 5);
|
|
unsigned y = extract32(insn, 13, 3);
|
|
unsigned rb = assemble_rb64(insn);
|
|
unsigned ra = assemble_ra64(insn);
|
|
return do_fcmp_s(ctx, ra, rb, y, c);
|
|
}
|
|
|
|
static ExitStatus trans_fcmp_d(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned c = extract32(insn, 0, 5);
|
|
unsigned y = extract32(insn, 13, 3);
|
|
unsigned rb = extract32(insn, 16, 5);
|
|
unsigned ra = extract32(insn, 21, 5);
|
|
TCGv_i64 ta, tb;
|
|
TCGv_i32 tc, ty;
|
|
|
|
nullify_over(ctx);
|
|
|
|
ta = load_frd0(ra);
|
|
tb = load_frd0(rb);
|
|
ty = tcg_const_i32(y);
|
|
tc = tcg_const_i32(c);
|
|
|
|
gen_helper_fcmp_d(cpu_env, ta, tb, ty, tc);
|
|
|
|
tcg_temp_free_i64(ta);
|
|
tcg_temp_free_i64(tb);
|
|
tcg_temp_free_i32(ty);
|
|
tcg_temp_free_i32(tc);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_ftest_t(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned y = extract32(insn, 13, 3);
|
|
unsigned cbit = (y ^ 1) - 1;
|
|
TCGv t;
|
|
|
|
nullify_over(ctx);
|
|
|
|
t = tcg_temp_new();
|
|
tcg_gen_ld32u_tl(t, cpu_env, offsetof(CPUHPPAState, fr0_shadow));
|
|
tcg_gen_extract_tl(t, t, 21 - cbit, 1);
|
|
ctx->null_cond = cond_make_0(TCG_COND_NE, t);
|
|
tcg_temp_free(t);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_ftest_q(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned c = extract32(insn, 0, 5);
|
|
int mask;
|
|
bool inv = false;
|
|
TCGv t;
|
|
|
|
nullify_over(ctx);
|
|
|
|
t = tcg_temp_new();
|
|
tcg_gen_ld32u_tl(t, cpu_env, offsetof(CPUHPPAState, fr0_shadow));
|
|
|
|
switch (c) {
|
|
case 0: /* simple */
|
|
tcg_gen_andi_tl(t, t, 0x4000000);
|
|
ctx->null_cond = cond_make_0(TCG_COND_NE, t);
|
|
goto done;
|
|
case 2: /* rej */
|
|
inv = true;
|
|
/* fallthru */
|
|
case 1: /* acc */
|
|
mask = 0x43ff800;
|
|
break;
|
|
case 6: /* rej8 */
|
|
inv = true;
|
|
/* fallthru */
|
|
case 5: /* acc8 */
|
|
mask = 0x43f8000;
|
|
break;
|
|
case 9: /* acc6 */
|
|
mask = 0x43e0000;
|
|
break;
|
|
case 13: /* acc4 */
|
|
mask = 0x4380000;
|
|
break;
|
|
case 17: /* acc2 */
|
|
mask = 0x4200000;
|
|
break;
|
|
default:
|
|
return gen_illegal(ctx);
|
|
}
|
|
if (inv) {
|
|
TCGv c = load_const(ctx, mask);
|
|
tcg_gen_or_tl(t, t, c);
|
|
ctx->null_cond = cond_make(TCG_COND_EQ, t, c);
|
|
} else {
|
|
tcg_gen_andi_tl(t, t, mask);
|
|
ctx->null_cond = cond_make_0(TCG_COND_EQ, t);
|
|
}
|
|
done:
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_xmpyu(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned rb = assemble_rb64(insn);
|
|
unsigned ra = assemble_ra64(insn);
|
|
TCGv_i64 a, b;
|
|
|
|
nullify_over(ctx);
|
|
|
|
a = load_frw0_i64(ra);
|
|
b = load_frw0_i64(rb);
|
|
tcg_gen_mul_i64(a, a, b);
|
|
save_frd(rt, a);
|
|
tcg_temp_free_i64(a);
|
|
tcg_temp_free_i64(b);
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
#define FOP_DED trans_fop_ded, .f.ded
|
|
#define FOP_DEDD trans_fop_dedd, .f.dedd
|
|
|
|
#define FOP_WEW trans_fop_wew_0c, .f.wew
|
|
#define FOP_DEW trans_fop_dew_0c, .f.dew
|
|
#define FOP_WED trans_fop_wed_0c, .f.wed
|
|
#define FOP_WEWW trans_fop_weww_0c, .f.weww
|
|
|
|
static const DisasInsn table_float_0c[] = {
|
|
/* floating point class zero */
|
|
{ 0x30004000, 0xfc1fffe0, FOP_WEW = gen_fcpy_s },
|
|
{ 0x30006000, 0xfc1fffe0, FOP_WEW = gen_fabs_s },
|
|
{ 0x30008000, 0xfc1fffe0, FOP_WEW = gen_helper_fsqrt_s },
|
|
{ 0x3000a000, 0xfc1fffe0, FOP_WEW = gen_helper_frnd_s },
|
|
{ 0x3000c000, 0xfc1fffe0, FOP_WEW = gen_fneg_s },
|
|
{ 0x3000e000, 0xfc1fffe0, FOP_WEW = gen_fnegabs_s },
|
|
|
|
{ 0x30004800, 0xfc1fffe0, FOP_DED = gen_fcpy_d },
|
|
{ 0x30006800, 0xfc1fffe0, FOP_DED = gen_fabs_d },
|
|
{ 0x30008800, 0xfc1fffe0, FOP_DED = gen_helper_fsqrt_d },
|
|
{ 0x3000a800, 0xfc1fffe0, FOP_DED = gen_helper_frnd_d },
|
|
{ 0x3000c800, 0xfc1fffe0, FOP_DED = gen_fneg_d },
|
|
{ 0x3000e800, 0xfc1fffe0, FOP_DED = gen_fnegabs_d },
|
|
|
|
/* floating point class three */
|
|
{ 0x30000600, 0xfc00ffe0, FOP_WEWW = gen_helper_fadd_s },
|
|
{ 0x30002600, 0xfc00ffe0, FOP_WEWW = gen_helper_fsub_s },
|
|
{ 0x30004600, 0xfc00ffe0, FOP_WEWW = gen_helper_fmpy_s },
|
|
{ 0x30006600, 0xfc00ffe0, FOP_WEWW = gen_helper_fdiv_s },
|
|
|
|
{ 0x30000e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fadd_d },
|
|
{ 0x30002e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fsub_d },
|
|
{ 0x30004e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fmpy_d },
|
|
{ 0x30006e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fdiv_d },
|
|
|
|
/* floating point class one */
|
|
/* float/float */
|
|
{ 0x30000a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_d_s },
|
|
{ 0x30002200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_s_d },
|
|
/* int/float */
|
|
{ 0x30008200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_w_s },
|
|
{ 0x30008a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_dw_s },
|
|
{ 0x3000a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_w_d },
|
|
{ 0x3000aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_dw_d },
|
|
/* float/int */
|
|
{ 0x30010200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_s_w },
|
|
{ 0x30010a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_d_w },
|
|
{ 0x30012200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_s_dw },
|
|
{ 0x30012a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_dw },
|
|
/* float/int truncate */
|
|
{ 0x30018200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_t_s_w },
|
|
{ 0x30018a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_t_d_w },
|
|
{ 0x3001a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_t_s_dw },
|
|
{ 0x3001aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_dw },
|
|
/* uint/float */
|
|
{ 0x30028200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_uw_s },
|
|
{ 0x30028a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_udw_s },
|
|
{ 0x3002a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_uw_d },
|
|
{ 0x3002aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_udw_d },
|
|
/* float/uint */
|
|
{ 0x30030200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_s_uw },
|
|
{ 0x30030a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_d_uw },
|
|
{ 0x30032200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_s_udw },
|
|
{ 0x30032a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_udw },
|
|
/* float/uint truncate */
|
|
{ 0x30038200, 0xfc1fffe0, FOP_WEW = gen_helper_fcnv_t_s_uw },
|
|
{ 0x30038a00, 0xfc1fffe0, FOP_WED = gen_helper_fcnv_t_d_uw },
|
|
{ 0x3003a200, 0xfc1fffe0, FOP_DEW = gen_helper_fcnv_t_s_udw },
|
|
{ 0x3003aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_udw },
|
|
|
|
/* floating point class two */
|
|
{ 0x30000400, 0xfc001fe0, trans_fcmp_s_0c },
|
|
{ 0x30000c00, 0xfc001fe0, trans_fcmp_d },
|
|
{ 0x30002420, 0xffffffe0, trans_ftest_q },
|
|
{ 0x30000420, 0xffff1fff, trans_ftest_t },
|
|
|
|
/* FID. Note that ra == rt == 0, which via fcpy puts 0 into fr0.
|
|
This is machine/revision == 0, which is reserved for simulator. */
|
|
{ 0x30000000, 0xffffffff, FOP_WEW = gen_fcpy_s },
|
|
};
|
|
|
|
#undef FOP_WEW
|
|
#undef FOP_DEW
|
|
#undef FOP_WED
|
|
#undef FOP_WEWW
|
|
#define FOP_WEW trans_fop_wew_0e, .f.wew
|
|
#define FOP_DEW trans_fop_dew_0e, .f.dew
|
|
#define FOP_WED trans_fop_wed_0e, .f.wed
|
|
#define FOP_WEWW trans_fop_weww_0e, .f.weww
|
|
|
|
static const DisasInsn table_float_0e[] = {
|
|
/* floating point class zero */
|
|
{ 0x38004000, 0xfc1fff20, FOP_WEW = gen_fcpy_s },
|
|
{ 0x38006000, 0xfc1fff20, FOP_WEW = gen_fabs_s },
|
|
{ 0x38008000, 0xfc1fff20, FOP_WEW = gen_helper_fsqrt_s },
|
|
{ 0x3800a000, 0xfc1fff20, FOP_WEW = gen_helper_frnd_s },
|
|
{ 0x3800c000, 0xfc1fff20, FOP_WEW = gen_fneg_s },
|
|
{ 0x3800e000, 0xfc1fff20, FOP_WEW = gen_fnegabs_s },
|
|
|
|
{ 0x38004800, 0xfc1fffe0, FOP_DED = gen_fcpy_d },
|
|
{ 0x38006800, 0xfc1fffe0, FOP_DED = gen_fabs_d },
|
|
{ 0x38008800, 0xfc1fffe0, FOP_DED = gen_helper_fsqrt_d },
|
|
{ 0x3800a800, 0xfc1fffe0, FOP_DED = gen_helper_frnd_d },
|
|
{ 0x3800c800, 0xfc1fffe0, FOP_DED = gen_fneg_d },
|
|
{ 0x3800e800, 0xfc1fffe0, FOP_DED = gen_fnegabs_d },
|
|
|
|
/* floating point class three */
|
|
{ 0x38000600, 0xfc00ef20, FOP_WEWW = gen_helper_fadd_s },
|
|
{ 0x38002600, 0xfc00ef20, FOP_WEWW = gen_helper_fsub_s },
|
|
{ 0x38004600, 0xfc00ef20, FOP_WEWW = gen_helper_fmpy_s },
|
|
{ 0x38006600, 0xfc00ef20, FOP_WEWW = gen_helper_fdiv_s },
|
|
|
|
{ 0x38000e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fadd_d },
|
|
{ 0x38002e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fsub_d },
|
|
{ 0x38004e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fmpy_d },
|
|
{ 0x38006e00, 0xfc00ffe0, FOP_DEDD = gen_helper_fdiv_d },
|
|
|
|
{ 0x38004700, 0xfc00ef60, trans_xmpyu },
|
|
|
|
/* floating point class one */
|
|
/* float/float */
|
|
{ 0x38000a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_d_s },
|
|
{ 0x38002200, 0xfc1fffc0, FOP_DEW = gen_helper_fcnv_s_d },
|
|
/* int/float */
|
|
{ 0x38008200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_w_s },
|
|
{ 0x38008a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_dw_s },
|
|
{ 0x3800a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_w_d },
|
|
{ 0x3800aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_dw_d },
|
|
/* float/int */
|
|
{ 0x38010200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_s_w },
|
|
{ 0x38010a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_d_w },
|
|
{ 0x38012200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_s_dw },
|
|
{ 0x38012a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_dw },
|
|
/* float/int truncate */
|
|
{ 0x38018200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_t_s_w },
|
|
{ 0x38018a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_t_d_w },
|
|
{ 0x3801a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_t_s_dw },
|
|
{ 0x3801aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_dw },
|
|
/* uint/float */
|
|
{ 0x38028200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_uw_s },
|
|
{ 0x38028a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_udw_s },
|
|
{ 0x3802a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_uw_d },
|
|
{ 0x3802aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_udw_d },
|
|
/* float/uint */
|
|
{ 0x38030200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_s_uw },
|
|
{ 0x38030a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_d_uw },
|
|
{ 0x38032200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_s_udw },
|
|
{ 0x38032a00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_d_udw },
|
|
/* float/uint truncate */
|
|
{ 0x38038200, 0xfc1ffe60, FOP_WEW = gen_helper_fcnv_t_s_uw },
|
|
{ 0x38038a00, 0xfc1fffa0, FOP_WED = gen_helper_fcnv_t_d_uw },
|
|
{ 0x3803a200, 0xfc1fff60, FOP_DEW = gen_helper_fcnv_t_s_udw },
|
|
{ 0x3803aa00, 0xfc1fffe0, FOP_DED = gen_helper_fcnv_t_d_udw },
|
|
|
|
/* floating point class two */
|
|
{ 0x38000400, 0xfc000f60, trans_fcmp_s_0e },
|
|
{ 0x38000c00, 0xfc001fe0, trans_fcmp_d },
|
|
};
|
|
|
|
#undef FOP_WEW
|
|
#undef FOP_DEW
|
|
#undef FOP_WED
|
|
#undef FOP_WEWW
|
|
#undef FOP_DED
|
|
#undef FOP_DEDD
|
|
|
|
/* Convert the fmpyadd single-precision register encodings to standard. */
|
|
static inline int fmpyadd_s_reg(unsigned r)
|
|
{
|
|
return (r & 16) * 2 + 16 + (r & 15);
|
|
}
|
|
|
|
static ExitStatus trans_fmpyadd(DisasContext *ctx, uint32_t insn, bool is_sub)
|
|
{
|
|
unsigned tm = extract32(insn, 0, 5);
|
|
unsigned f = extract32(insn, 5, 1);
|
|
unsigned ra = extract32(insn, 6, 5);
|
|
unsigned ta = extract32(insn, 11, 5);
|
|
unsigned rm2 = extract32(insn, 16, 5);
|
|
unsigned rm1 = extract32(insn, 21, 5);
|
|
|
|
nullify_over(ctx);
|
|
|
|
/* Independent multiply & add/sub, with undefined behaviour
|
|
if outputs overlap inputs. */
|
|
if (f == 0) {
|
|
tm = fmpyadd_s_reg(tm);
|
|
ra = fmpyadd_s_reg(ra);
|
|
ta = fmpyadd_s_reg(ta);
|
|
rm2 = fmpyadd_s_reg(rm2);
|
|
rm1 = fmpyadd_s_reg(rm1);
|
|
do_fop_weww(ctx, tm, rm1, rm2, gen_helper_fmpy_s);
|
|
do_fop_weww(ctx, ta, ta, ra,
|
|
is_sub ? gen_helper_fsub_s : gen_helper_fadd_s);
|
|
} else {
|
|
do_fop_dedd(ctx, tm, rm1, rm2, gen_helper_fmpy_d);
|
|
do_fop_dedd(ctx, ta, ta, ra,
|
|
is_sub ? gen_helper_fsub_d : gen_helper_fadd_d);
|
|
}
|
|
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_fmpyfadd_s(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = assemble_rt64(insn);
|
|
unsigned neg = extract32(insn, 5, 1);
|
|
unsigned rm1 = assemble_ra64(insn);
|
|
unsigned rm2 = assemble_rb64(insn);
|
|
unsigned ra3 = assemble_rc64(insn);
|
|
TCGv_i32 a, b, c;
|
|
|
|
nullify_over(ctx);
|
|
a = load_frw0_i32(rm1);
|
|
b = load_frw0_i32(rm2);
|
|
c = load_frw0_i32(ra3);
|
|
|
|
if (neg) {
|
|
gen_helper_fmpynfadd_s(a, cpu_env, a, b, c);
|
|
} else {
|
|
gen_helper_fmpyfadd_s(a, cpu_env, a, b, c);
|
|
}
|
|
|
|
tcg_temp_free_i32(b);
|
|
tcg_temp_free_i32(c);
|
|
save_frw_i32(rt, a);
|
|
tcg_temp_free_i32(a);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static ExitStatus trans_fmpyfadd_d(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn *di)
|
|
{
|
|
unsigned rt = extract32(insn, 0, 5);
|
|
unsigned neg = extract32(insn, 5, 1);
|
|
unsigned rm1 = extract32(insn, 21, 5);
|
|
unsigned rm2 = extract32(insn, 16, 5);
|
|
unsigned ra3 = assemble_rc64(insn);
|
|
TCGv_i64 a, b, c;
|
|
|
|
nullify_over(ctx);
|
|
a = load_frd0(rm1);
|
|
b = load_frd0(rm2);
|
|
c = load_frd0(ra3);
|
|
|
|
if (neg) {
|
|
gen_helper_fmpynfadd_d(a, cpu_env, a, b, c);
|
|
} else {
|
|
gen_helper_fmpyfadd_d(a, cpu_env, a, b, c);
|
|
}
|
|
|
|
tcg_temp_free_i64(b);
|
|
tcg_temp_free_i64(c);
|
|
save_frd(rt, a);
|
|
tcg_temp_free_i64(a);
|
|
return nullify_end(ctx, NO_EXIT);
|
|
}
|
|
|
|
static const DisasInsn table_fp_fused[] = {
|
|
{ 0xb8000000u, 0xfc000800u, trans_fmpyfadd_s },
|
|
{ 0xb8000800u, 0xfc0019c0u, trans_fmpyfadd_d }
|
|
};
|
|
|
|
static ExitStatus translate_table_int(DisasContext *ctx, uint32_t insn,
|
|
const DisasInsn table[], size_t n)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < n; ++i) {
|
|
if ((insn & table[i].mask) == table[i].insn) {
|
|
return table[i].trans(ctx, insn, &table[i]);
|
|
}
|
|
}
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
#define translate_table(ctx, insn, table) \
|
|
translate_table_int(ctx, insn, table, ARRAY_SIZE(table))
|
|
|
|
static ExitStatus translate_one(DisasContext *ctx, uint32_t insn)
|
|
{
|
|
uint32_t opc = extract32(insn, 26, 6);
|
|
|
|
switch (opc) {
|
|
case 0x00: /* system op */
|
|
return translate_table(ctx, insn, table_system);
|
|
case 0x01:
|
|
return translate_table(ctx, insn, table_mem_mgmt);
|
|
case 0x02:
|
|
return translate_table(ctx, insn, table_arith_log);
|
|
case 0x03:
|
|
return translate_table(ctx, insn, table_index_mem);
|
|
case 0x06:
|
|
return trans_fmpyadd(ctx, insn, false);
|
|
case 0x08:
|
|
return trans_ldil(ctx, insn);
|
|
case 0x09:
|
|
return trans_copr_w(ctx, insn);
|
|
case 0x0A:
|
|
return trans_addil(ctx, insn);
|
|
case 0x0B:
|
|
return trans_copr_dw(ctx, insn);
|
|
case 0x0C:
|
|
return translate_table(ctx, insn, table_float_0c);
|
|
case 0x0D:
|
|
return trans_ldo(ctx, insn);
|
|
case 0x0E:
|
|
return translate_table(ctx, insn, table_float_0e);
|
|
|
|
case 0x10:
|
|
return trans_load(ctx, insn, false, MO_UB);
|
|
case 0x11:
|
|
return trans_load(ctx, insn, false, MO_TEUW);
|
|
case 0x12:
|
|
return trans_load(ctx, insn, false, MO_TEUL);
|
|
case 0x13:
|
|
return trans_load(ctx, insn, true, MO_TEUL);
|
|
case 0x16:
|
|
return trans_fload_mod(ctx, insn);
|
|
case 0x17:
|
|
return trans_load_w(ctx, insn);
|
|
case 0x18:
|
|
return trans_store(ctx, insn, false, MO_UB);
|
|
case 0x19:
|
|
return trans_store(ctx, insn, false, MO_TEUW);
|
|
case 0x1A:
|
|
return trans_store(ctx, insn, false, MO_TEUL);
|
|
case 0x1B:
|
|
return trans_store(ctx, insn, true, MO_TEUL);
|
|
case 0x1E:
|
|
return trans_fstore_mod(ctx, insn);
|
|
case 0x1F:
|
|
return trans_store_w(ctx, insn);
|
|
|
|
case 0x20:
|
|
return trans_cmpb(ctx, insn, true, false, false);
|
|
case 0x21:
|
|
return trans_cmpb(ctx, insn, true, true, false);
|
|
case 0x22:
|
|
return trans_cmpb(ctx, insn, false, false, false);
|
|
case 0x23:
|
|
return trans_cmpb(ctx, insn, false, true, false);
|
|
case 0x24:
|
|
return trans_cmpiclr(ctx, insn);
|
|
case 0x25:
|
|
return trans_subi(ctx, insn);
|
|
case 0x26:
|
|
return trans_fmpyadd(ctx, insn, true);
|
|
case 0x27:
|
|
return trans_cmpb(ctx, insn, true, false, true);
|
|
case 0x28:
|
|
return trans_addb(ctx, insn, true, false);
|
|
case 0x29:
|
|
return trans_addb(ctx, insn, true, true);
|
|
case 0x2A:
|
|
return trans_addb(ctx, insn, false, false);
|
|
case 0x2B:
|
|
return trans_addb(ctx, insn, false, true);
|
|
case 0x2C:
|
|
case 0x2D:
|
|
return trans_addi(ctx, insn);
|
|
case 0x2E:
|
|
return translate_table(ctx, insn, table_fp_fused);
|
|
case 0x2F:
|
|
return trans_cmpb(ctx, insn, false, false, true);
|
|
|
|
case 0x30:
|
|
case 0x31:
|
|
return trans_bb(ctx, insn);
|
|
case 0x32:
|
|
return trans_movb(ctx, insn, false);
|
|
case 0x33:
|
|
return trans_movb(ctx, insn, true);
|
|
case 0x34:
|
|
return translate_table(ctx, insn, table_sh_ex);
|
|
case 0x35:
|
|
return translate_table(ctx, insn, table_depw);
|
|
case 0x38:
|
|
return trans_be(ctx, insn, false);
|
|
case 0x39:
|
|
return trans_be(ctx, insn, true);
|
|
case 0x3A:
|
|
return translate_table(ctx, insn, table_branch);
|
|
|
|
case 0x04: /* spopn */
|
|
case 0x05: /* diag */
|
|
case 0x0F: /* product specific */
|
|
break;
|
|
|
|
case 0x07: /* unassigned */
|
|
case 0x15: /* unassigned */
|
|
case 0x1D: /* unassigned */
|
|
case 0x37: /* unassigned */
|
|
case 0x3F: /* unassigned */
|
|
default:
|
|
break;
|
|
}
|
|
return gen_illegal(ctx);
|
|
}
|
|
|
|
void gen_intermediate_code(CPUHPPAState *env, struct TranslationBlock *tb)
|
|
{
|
|
HPPACPU *cpu = hppa_env_get_cpu(env);
|
|
CPUState *cs = CPU(cpu);
|
|
DisasContext ctx;
|
|
ExitStatus ret;
|
|
int num_insns, max_insns, i;
|
|
|
|
ctx.tb = tb;
|
|
ctx.cs = cs;
|
|
ctx.iaoq_f = tb->pc;
|
|
ctx.iaoq_b = tb->cs_base;
|
|
ctx.singlestep_enabled = cs->singlestep_enabled;
|
|
|
|
ctx.ntemps = 0;
|
|
for (i = 0; i < ARRAY_SIZE(ctx.temps); ++i) {
|
|
TCGV_UNUSED(ctx.temps[i]);
|
|
}
|
|
|
|
/* Compute the maximum number of insns to execute, as bounded by
|
|
(1) icount, (2) single-stepping, (3) branch delay slots, or
|
|
(4) the number of insns remaining on the current page. */
|
|
max_insns = tb->cflags & CF_COUNT_MASK;
|
|
if (max_insns == 0) {
|
|
max_insns = CF_COUNT_MASK;
|
|
}
|
|
if (ctx.singlestep_enabled || singlestep) {
|
|
max_insns = 1;
|
|
} else if (max_insns > TCG_MAX_INSNS) {
|
|
max_insns = TCG_MAX_INSNS;
|
|
}
|
|
|
|
num_insns = 0;
|
|
gen_tb_start(tb);
|
|
|
|
/* Seed the nullification status from PSW[N], as shown in TB->FLAGS. */
|
|
ctx.null_cond = cond_make_f();
|
|
ctx.psw_n_nonzero = false;
|
|
if (tb->flags & 1) {
|
|
ctx.null_cond.c = TCG_COND_ALWAYS;
|
|
ctx.psw_n_nonzero = true;
|
|
}
|
|
ctx.null_lab = NULL;
|
|
|
|
do {
|
|
tcg_gen_insn_start(ctx.iaoq_f, ctx.iaoq_b);
|
|
num_insns++;
|
|
|
|
if (unlikely(cpu_breakpoint_test(cs, ctx.iaoq_f, BP_ANY))) {
|
|
ret = gen_excp(&ctx, EXCP_DEBUG);
|
|
break;
|
|
}
|
|
if (num_insns == max_insns && (tb->cflags & CF_LAST_IO)) {
|
|
gen_io_start();
|
|
}
|
|
|
|
if (ctx.iaoq_f < TARGET_PAGE_SIZE) {
|
|
ret = do_page_zero(&ctx);
|
|
assert(ret != NO_EXIT);
|
|
} else {
|
|
/* Always fetch the insn, even if nullified, so that we check
|
|
the page permissions for execute. */
|
|
uint32_t insn = cpu_ldl_code(env, ctx.iaoq_f);
|
|
|
|
/* Set up the IA queue for the next insn.
|
|
This will be overwritten by a branch. */
|
|
if (ctx.iaoq_b == -1) {
|
|
ctx.iaoq_n = -1;
|
|
ctx.iaoq_n_var = get_temp(&ctx);
|
|
tcg_gen_addi_tl(ctx.iaoq_n_var, cpu_iaoq_b, 4);
|
|
} else {
|
|
ctx.iaoq_n = ctx.iaoq_b + 4;
|
|
TCGV_UNUSED(ctx.iaoq_n_var);
|
|
}
|
|
|
|
if (unlikely(ctx.null_cond.c == TCG_COND_ALWAYS)) {
|
|
ctx.null_cond.c = TCG_COND_NEVER;
|
|
ret = NO_EXIT;
|
|
} else {
|
|
ret = translate_one(&ctx, insn);
|
|
assert(ctx.null_lab == NULL);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ctx.ntemps; ++i) {
|
|
tcg_temp_free(ctx.temps[i]);
|
|
TCGV_UNUSED(ctx.temps[i]);
|
|
}
|
|
ctx.ntemps = 0;
|
|
|
|
/* If we see non-linear instructions, exhaust instruction count,
|
|
or run out of buffer space, stop generation. */
|
|
/* ??? The non-linear instruction restriction is purely due to
|
|
the debugging dump. Otherwise we *could* follow unconditional
|
|
branches within the same page. */
|
|
if (ret == NO_EXIT
|
|
&& (ctx.iaoq_b != ctx.iaoq_f + 4
|
|
|| num_insns >= max_insns
|
|
|| tcg_op_buf_full())) {
|
|
if (ctx.null_cond.c == TCG_COND_NEVER
|
|
|| ctx.null_cond.c == TCG_COND_ALWAYS) {
|
|
nullify_set(&ctx, ctx.null_cond.c == TCG_COND_ALWAYS);
|
|
gen_goto_tb(&ctx, 0, ctx.iaoq_b, ctx.iaoq_n);
|
|
ret = EXIT_GOTO_TB;
|
|
} else {
|
|
ret = EXIT_IAQ_N_STALE;
|
|
}
|
|
}
|
|
|
|
ctx.iaoq_f = ctx.iaoq_b;
|
|
ctx.iaoq_b = ctx.iaoq_n;
|
|
if (ret == EXIT_NORETURN
|
|
|| ret == EXIT_GOTO_TB
|
|
|| ret == EXIT_IAQ_N_UPDATED) {
|
|
break;
|
|
}
|
|
if (ctx.iaoq_f == -1) {
|
|
tcg_gen_mov_tl(cpu_iaoq_f, cpu_iaoq_b);
|
|
copy_iaoq_entry(cpu_iaoq_b, ctx.iaoq_n, ctx.iaoq_n_var);
|
|
nullify_save(&ctx);
|
|
ret = EXIT_IAQ_N_UPDATED;
|
|
break;
|
|
}
|
|
if (ctx.iaoq_b == -1) {
|
|
tcg_gen_mov_tl(cpu_iaoq_b, ctx.iaoq_n_var);
|
|
}
|
|
} while (ret == NO_EXIT);
|
|
|
|
if (tb->cflags & CF_LAST_IO) {
|
|
gen_io_end();
|
|
}
|
|
|
|
switch (ret) {
|
|
case EXIT_GOTO_TB:
|
|
case EXIT_NORETURN:
|
|
break;
|
|
case EXIT_IAQ_N_STALE:
|
|
copy_iaoq_entry(cpu_iaoq_f, ctx.iaoq_f, cpu_iaoq_f);
|
|
copy_iaoq_entry(cpu_iaoq_b, ctx.iaoq_b, cpu_iaoq_b);
|
|
nullify_save(&ctx);
|
|
/* FALLTHRU */
|
|
case EXIT_IAQ_N_UPDATED:
|
|
if (ctx.singlestep_enabled) {
|
|
gen_excp_1(EXCP_DEBUG);
|
|
} else {
|
|
tcg_gen_lookup_and_goto_ptr(cpu_iaoq_f);
|
|
}
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
gen_tb_end(tb, num_insns);
|
|
|
|
tb->size = num_insns * 4;
|
|
tb->icount = num_insns;
|
|
|
|
#ifdef DEBUG_DISAS
|
|
if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM)
|
|
&& qemu_log_in_addr_range(tb->pc)) {
|
|
qemu_log_lock();
|
|
switch (tb->pc) {
|
|
case 0x00:
|
|
qemu_log("IN:\n0x00000000: (null)\n\n");
|
|
break;
|
|
case 0xb0:
|
|
qemu_log("IN:\n0x000000b0: light-weight-syscall\n\n");
|
|
break;
|
|
case 0xe0:
|
|
qemu_log("IN:\n0x000000e0: set-thread-pointer-syscall\n\n");
|
|
break;
|
|
case 0x100:
|
|
qemu_log("IN:\n0x00000100: syscall\n\n");
|
|
break;
|
|
default:
|
|
qemu_log("IN: %s\n", lookup_symbol(tb->pc));
|
|
log_target_disas(cs, tb->pc, tb->size, 1);
|
|
qemu_log("\n");
|
|
break;
|
|
}
|
|
qemu_log_unlock();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void restore_state_to_opc(CPUHPPAState *env, TranslationBlock *tb,
|
|
target_ulong *data)
|
|
{
|
|
env->iaoq_f = data[0];
|
|
if (data[1] != -1) {
|
|
env->iaoq_b = data[1];
|
|
}
|
|
/* Since we were executing the instruction at IAOQ_F, and took some
|
|
sort of action that provoked the cpu_restore_state, we can infer
|
|
that the instruction was not nullified. */
|
|
env->psw_n = 0;
|
|
}
|