blob: 2f8d407a82b7d41078ed23fb7c711b6256ac283f [file] [log] [blame]
/*
* 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 "exec/translator.h"
#include "trace-tcg.h"
#include "exec/log.h"
/* Since we have a distinction between register size and address size,
we need to redefine all of these. */
#undef TCGv
#undef tcg_temp_new
#undef tcg_global_reg_new
#undef tcg_global_mem_new
#undef tcg_temp_local_new
#undef tcg_temp_free
#if TARGET_LONG_BITS == 64
#define TCGv_tl TCGv_i64
#define tcg_temp_new_tl tcg_temp_new_i64
#define tcg_temp_free_tl tcg_temp_free_i64
#if TARGET_REGISTER_BITS == 64
#define tcg_gen_extu_reg_tl tcg_gen_mov_i64
#else
#define tcg_gen_extu_reg_tl tcg_gen_extu_i32_i64
#endif
#else
#define TCGv_tl TCGv_i32
#define tcg_temp_new_tl tcg_temp_new_i32
#define tcg_temp_free_tl tcg_temp_free_i32
#define tcg_gen_extu_reg_tl tcg_gen_mov_i32
#endif
#if TARGET_REGISTER_BITS == 64
#define TCGv_reg TCGv_i64
#define tcg_temp_new tcg_temp_new_i64
#define tcg_global_reg_new tcg_global_reg_new_i64
#define tcg_global_mem_new tcg_global_mem_new_i64
#define tcg_temp_local_new tcg_temp_local_new_i64
#define tcg_temp_free tcg_temp_free_i64
#define tcg_gen_movi_reg tcg_gen_movi_i64
#define tcg_gen_mov_reg tcg_gen_mov_i64
#define tcg_gen_ld8u_reg tcg_gen_ld8u_i64
#define tcg_gen_ld8s_reg tcg_gen_ld8s_i64
#define tcg_gen_ld16u_reg tcg_gen_ld16u_i64
#define tcg_gen_ld16s_reg tcg_gen_ld16s_i64
#define tcg_gen_ld32u_reg tcg_gen_ld32u_i64
#define tcg_gen_ld32s_reg tcg_gen_ld32s_i64
#define tcg_gen_ld_reg tcg_gen_ld_i64
#define tcg_gen_st8_reg tcg_gen_st8_i64
#define tcg_gen_st16_reg tcg_gen_st16_i64
#define tcg_gen_st32_reg tcg_gen_st32_i64
#define tcg_gen_st_reg tcg_gen_st_i64
#define tcg_gen_add_reg tcg_gen_add_i64
#define tcg_gen_addi_reg tcg_gen_addi_i64
#define tcg_gen_sub_reg tcg_gen_sub_i64
#define tcg_gen_neg_reg tcg_gen_neg_i64
#define tcg_gen_subfi_reg tcg_gen_subfi_i64
#define tcg_gen_subi_reg tcg_gen_subi_i64
#define tcg_gen_and_reg tcg_gen_and_i64
#define tcg_gen_andi_reg tcg_gen_andi_i64
#define tcg_gen_or_reg tcg_gen_or_i64
#define tcg_gen_ori_reg tcg_gen_ori_i64
#define tcg_gen_xor_reg tcg_gen_xor_i64
#define tcg_gen_xori_reg tcg_gen_xori_i64
#define tcg_gen_not_reg tcg_gen_not_i64
#define tcg_gen_shl_reg tcg_gen_shl_i64
#define tcg_gen_shli_reg tcg_gen_shli_i64
#define tcg_gen_shr_reg tcg_gen_shr_i64
#define tcg_gen_shri_reg tcg_gen_shri_i64
#define tcg_gen_sar_reg tcg_gen_sar_i64
#define tcg_gen_sari_reg tcg_gen_sari_i64
#define tcg_gen_brcond_reg tcg_gen_brcond_i64
#define tcg_gen_brcondi_reg tcg_gen_brcondi_i64
#define tcg_gen_setcond_reg tcg_gen_setcond_i64
#define tcg_gen_setcondi_reg tcg_gen_setcondi_i64
#define tcg_gen_mul_reg tcg_gen_mul_i64
#define tcg_gen_muli_reg tcg_gen_muli_i64
#define tcg_gen_div_reg tcg_gen_div_i64
#define tcg_gen_rem_reg tcg_gen_rem_i64
#define tcg_gen_divu_reg tcg_gen_divu_i64
#define tcg_gen_remu_reg tcg_gen_remu_i64
#define tcg_gen_discard_reg tcg_gen_discard_i64
#define tcg_gen_trunc_reg_i32 tcg_gen_extrl_i64_i32
#define tcg_gen_trunc_i64_reg tcg_gen_mov_i64
#define tcg_gen_extu_i32_reg tcg_gen_extu_i32_i64
#define tcg_gen_ext_i32_reg tcg_gen_ext_i32_i64
#define tcg_gen_extu_reg_i64 tcg_gen_mov_i64
#define tcg_gen_ext_reg_i64 tcg_gen_mov_i64
#define tcg_gen_ext8u_reg tcg_gen_ext8u_i64
#define tcg_gen_ext8s_reg tcg_gen_ext8s_i64
#define tcg_gen_ext16u_reg tcg_gen_ext16u_i64
#define tcg_gen_ext16s_reg tcg_gen_ext16s_i64
#define tcg_gen_ext32u_reg tcg_gen_ext32u_i64
#define tcg_gen_ext32s_reg tcg_gen_ext32s_i64
#define tcg_gen_bswap16_reg tcg_gen_bswap16_i64
#define tcg_gen_bswap32_reg tcg_gen_bswap32_i64
#define tcg_gen_bswap64_reg tcg_gen_bswap64_i64
#define tcg_gen_concat_reg_i64 tcg_gen_concat32_i64
#define tcg_gen_andc_reg tcg_gen_andc_i64
#define tcg_gen_eqv_reg tcg_gen_eqv_i64
#define tcg_gen_nand_reg tcg_gen_nand_i64
#define tcg_gen_nor_reg tcg_gen_nor_i64
#define tcg_gen_orc_reg tcg_gen_orc_i64
#define tcg_gen_clz_reg tcg_gen_clz_i64
#define tcg_gen_ctz_reg tcg_gen_ctz_i64
#define tcg_gen_clzi_reg tcg_gen_clzi_i64
#define tcg_gen_ctzi_reg tcg_gen_ctzi_i64
#define tcg_gen_clrsb_reg tcg_gen_clrsb_i64
#define tcg_gen_ctpop_reg tcg_gen_ctpop_i64
#define tcg_gen_rotl_reg tcg_gen_rotl_i64
#define tcg_gen_rotli_reg tcg_gen_rotli_i64
#define tcg_gen_rotr_reg tcg_gen_rotr_i64
#define tcg_gen_rotri_reg tcg_gen_rotri_i64
#define tcg_gen_deposit_reg tcg_gen_deposit_i64
#define tcg_gen_deposit_z_reg tcg_gen_deposit_z_i64
#define tcg_gen_extract_reg tcg_gen_extract_i64
#define tcg_gen_sextract_reg tcg_gen_sextract_i64
#define tcg_const_reg tcg_const_i64
#define tcg_const_local_reg tcg_const_local_i64
#define tcg_gen_movcond_reg tcg_gen_movcond_i64
#define tcg_gen_add2_reg tcg_gen_add2_i64
#define tcg_gen_sub2_reg tcg_gen_sub2_i64
#define tcg_gen_qemu_ld_reg tcg_gen_qemu_ld_i64
#define tcg_gen_qemu_st_reg tcg_gen_qemu_st_i64
#define tcg_gen_atomic_xchg_reg tcg_gen_atomic_xchg_i64
#define tcg_gen_trunc_reg_ptr tcg_gen_trunc_i64_ptr
#else
#define TCGv_reg TCGv_i32
#define tcg_temp_new tcg_temp_new_i32
#define tcg_global_reg_new tcg_global_reg_new_i32
#define tcg_global_mem_new tcg_global_mem_new_i32
#define tcg_temp_local_new tcg_temp_local_new_i32
#define tcg_temp_free tcg_temp_free_i32
#define tcg_gen_movi_reg tcg_gen_movi_i32
#define tcg_gen_mov_reg tcg_gen_mov_i32
#define tcg_gen_ld8u_reg tcg_gen_ld8u_i32
#define tcg_gen_ld8s_reg tcg_gen_ld8s_i32
#define tcg_gen_ld16u_reg tcg_gen_ld16u_i32
#define tcg_gen_ld16s_reg tcg_gen_ld16s_i32
#define tcg_gen_ld32u_reg tcg_gen_ld_i32
#define tcg_gen_ld32s_reg tcg_gen_ld_i32
#define tcg_gen_ld_reg tcg_gen_ld_i32
#define tcg_gen_st8_reg tcg_gen_st8_i32
#define tcg_gen_st16_reg tcg_gen_st16_i32
#define tcg_gen_st32_reg tcg_gen_st32_i32
#define tcg_gen_st_reg tcg_gen_st_i32
#define tcg_gen_add_reg tcg_gen_add_i32
#define tcg_gen_addi_reg tcg_gen_addi_i32
#define tcg_gen_sub_reg tcg_gen_sub_i32
#define tcg_gen_neg_reg tcg_gen_neg_i32
#define tcg_gen_subfi_reg tcg_gen_subfi_i32
#define tcg_gen_subi_reg tcg_gen_subi_i32
#define tcg_gen_and_reg tcg_gen_and_i32
#define tcg_gen_andi_reg tcg_gen_andi_i32
#define tcg_gen_or_reg tcg_gen_or_i32
#define tcg_gen_ori_reg tcg_gen_ori_i32
#define tcg_gen_xor_reg tcg_gen_xor_i32
#define tcg_gen_xori_reg tcg_gen_xori_i32
#define tcg_gen_not_reg tcg_gen_not_i32
#define tcg_gen_shl_reg tcg_gen_shl_i32
#define tcg_gen_shli_reg tcg_gen_shli_i32
#define tcg_gen_shr_reg tcg_gen_shr_i32
#define tcg_gen_shri_reg tcg_gen_shri_i32
#define tcg_gen_sar_reg tcg_gen_sar_i32
#define tcg_gen_sari_reg tcg_gen_sari_i32
#define tcg_gen_brcond_reg tcg_gen_brcond_i32
#define tcg_gen_brcondi_reg tcg_gen_brcondi_i32
#define tcg_gen_setcond_reg tcg_gen_setcond_i32
#define tcg_gen_setcondi_reg tcg_gen_setcondi_i32
#define tcg_gen_mul_reg tcg_gen_mul_i32
#define tcg_gen_muli_reg tcg_gen_muli_i32
#define tcg_gen_div_reg tcg_gen_div_i32
#define tcg_gen_rem_reg tcg_gen_rem_i32
#define tcg_gen_divu_reg tcg_gen_divu_i32
#define tcg_gen_remu_reg tcg_gen_remu_i32
#define tcg_gen_discard_reg tcg_gen_discard_i32
#define tcg_gen_trunc_reg_i32 tcg_gen_mov_i32
#define tcg_gen_trunc_i64_reg tcg_gen_extrl_i64_i32
#define tcg_gen_extu_i32_reg tcg_gen_mov_i32
#define tcg_gen_ext_i32_reg tcg_gen_mov_i32
#define tcg_gen_extu_reg_i64 tcg_gen_extu_i32_i64
#define tcg_gen_ext_reg_i64 tcg_gen_ext_i32_i64
#define tcg_gen_ext8u_reg tcg_gen_ext8u_i32
#define tcg_gen_ext8s_reg tcg_gen_ext8s_i32
#define tcg_gen_ext16u_reg tcg_gen_ext16u_i32
#define tcg_gen_ext16s_reg tcg_gen_ext16s_i32
#define tcg_gen_ext32u_reg tcg_gen_mov_i32
#define tcg_gen_ext32s_reg tcg_gen_mov_i32
#define tcg_gen_bswap16_reg tcg_gen_bswap16_i32
#define tcg_gen_bswap32_reg tcg_gen_bswap32_i32
#define tcg_gen_concat_reg_i64 tcg_gen_concat_i32_i64
#define tcg_gen_andc_reg tcg_gen_andc_i32
#define tcg_gen_eqv_reg tcg_gen_eqv_i32
#define tcg_gen_nand_reg tcg_gen_nand_i32
#define tcg_gen_nor_reg tcg_gen_nor_i32
#define tcg_gen_orc_reg tcg_gen_orc_i32
#define tcg_gen_clz_reg tcg_gen_clz_i32
#define tcg_gen_ctz_reg tcg_gen_ctz_i32
#define tcg_gen_clzi_reg tcg_gen_clzi_i32
#define tcg_gen_ctzi_reg tcg_gen_ctzi_i32
#define tcg_gen_clrsb_reg tcg_gen_clrsb_i32
#define tcg_gen_ctpop_reg tcg_gen_ctpop_i32
#define tcg_gen_rotl_reg tcg_gen_rotl_i32
#define tcg_gen_rotli_reg tcg_gen_rotli_i32
#define tcg_gen_rotr_reg tcg_gen_rotr_i32
#define tcg_gen_rotri_reg tcg_gen_rotri_i32
#define tcg_gen_deposit_reg tcg_gen_deposit_i32
#define tcg_gen_deposit_z_reg tcg_gen_deposit_z_i32
#define tcg_gen_extract_reg tcg_gen_extract_i32
#define tcg_gen_sextract_reg tcg_gen_sextract_i32
#define tcg_const_reg tcg_const_i32
#define tcg_const_local_reg tcg_const_local_i32
#define tcg_gen_movcond_reg tcg_gen_movcond_i32
#define tcg_gen_add2_reg tcg_gen_add2_i32
#define tcg_gen_sub2_reg tcg_gen_sub2_i32
#define tcg_gen_qemu_ld_reg tcg_gen_qemu_ld_i32
#define tcg_gen_qemu_st_reg tcg_gen_qemu_st_i32
#define tcg_gen_atomic_xchg_reg tcg_gen_atomic_xchg_i32
#define tcg_gen_trunc_reg_ptr tcg_gen_ext_i32_ptr
#endif /* TARGET_REGISTER_BITS */
typedef struct DisasCond {
TCGCond c;
TCGv_reg a0, a1;
bool a0_is_n;
bool a1_is_0;
} DisasCond;
typedef struct DisasContext {
DisasContextBase base;
CPUState *cs;
target_ureg iaoq_f;
target_ureg iaoq_b;
target_ureg iaoq_n;
TCGv_reg iaoq_n_var;
int ntempr, ntempl;
TCGv_reg tempr[8];
TCGv_tl templ[4];
DisasCond null_cond;
TCGLabel *null_lab;
uint32_t insn;
uint32_t tb_flags;
int mmu_idx;
int privilege;
bool psw_n_nonzero;
} DisasContext;
/* Note that ssm/rsm instructions number PSW_W and PSW_E differently. */
static int expand_sm_imm(DisasContext *ctx, int val)
{
if (val & PSW_SM_E) {
val = (val & ~PSW_SM_E) | PSW_E;
}
if (val & PSW_SM_W) {
val = (val & ~PSW_SM_W) | PSW_W;
}
return val;
}
/* Inverted space register indicates 0 means sr0 not inferred from base. */
static int expand_sr3x(DisasContext *ctx, int val)
{
return ~val;
}
/* Convert the M:A bits within a memory insn to the tri-state value
we use for the final M. */
static int ma_to_m(DisasContext *ctx, int val)
{
return val & 2 ? (val & 1 ? -1 : 1) : 0;
}
/* Convert the sign of the displacement to a pre or post-modify. */
static int pos_to_m(DisasContext *ctx, int val)
{
return val ? 1 : -1;
}
static int neg_to_m(DisasContext *ctx, int val)
{
return val ? -1 : 1;
}
/* Used for branch targets and fp memory ops. */
static int expand_shl2(DisasContext *ctx, int val)
{
return val << 2;
}
/* Used for fp memory ops. */
static int expand_shl3(DisasContext *ctx, int val)
{
return val << 3;
}
/* Used for assemble_21. */
static int expand_shl11(DisasContext *ctx, int val)
{
return val << 11;
}
/* Include the auto-generated decoder. */
#include "decode.inc.c"
/* 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. */
#define DISAS_IAQ_N_UPDATED DISAS_TARGET_0
/* We are exiting the TB, but have neither emitted a goto_tb, nor
updated the iaq for the next instruction to be executed. */
#define DISAS_IAQ_N_STALE DISAS_TARGET_1
/* Similarly, but we want to return to the main loop immediately
to recognize unmasked interrupts. */
#define DISAS_IAQ_N_STALE_EXIT DISAS_TARGET_2
#define DISAS_EXIT DISAS_TARGET_3
/* global register indexes */
static TCGv_reg cpu_gr[32];
static TCGv_i64 cpu_sr[4];
static TCGv_i64 cpu_srH;
static TCGv_reg cpu_iaoq_f;
static TCGv_reg cpu_iaoq_b;
static TCGv_i64 cpu_iasq_f;
static TCGv_i64 cpu_iasq_b;
static TCGv_reg cpu_sar;
static TCGv_reg cpu_psw_n;
static TCGv_reg cpu_psw_v;
static TCGv_reg cpu_psw_cb;
static TCGv_reg cpu_psw_cb_msb;
#include "exec/gen-icount.h"
void hppa_translate_init(void)
{
#define DEF_VAR(V) { &cpu_##V, #V, offsetof(CPUHPPAState, V) }
typedef struct { TCGv_reg *var; const char *name; int ofs; } GlobalVar;
static const GlobalVar vars[] = {
{ &cpu_sar, "sar", offsetof(CPUHPPAState, cr[CR_SAR]) },
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"
};
/* SR[4-7] are not global registers so that we can index them. */
static const char sr_names[5][4] = {
"sr0", "sr1", "sr2", "sr3", "srH"
};
int i;
cpu_gr[0] = NULL;
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 < 4; i++) {
cpu_sr[i] = tcg_global_mem_new_i64(cpu_env,
offsetof(CPUHPPAState, sr[i]),
sr_names[i]);
}
cpu_srH = tcg_global_mem_new_i64(cpu_env,
offsetof(CPUHPPAState, sr[4]),
sr_names[4]);
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);
}
cpu_iasq_f = tcg_global_mem_new_i64(cpu_env,
offsetof(CPUHPPAState, iasq_f),
"iasq_f");
cpu_iasq_b = tcg_global_mem_new_i64(cpu_env,
offsetof(CPUHPPAState, iasq_b),
"iasq_b");
}
static DisasCond cond_make_f(void)
{
return (DisasCond){
.c = TCG_COND_NEVER,
.a0 = NULL,
.a1 = NULL,
};
}
static DisasCond cond_make_t(void)
{
return (DisasCond){
.c = TCG_COND_ALWAYS,
.a0 = NULL,
.a1 = NULL,
};
}
static DisasCond cond_make_n(void)
{
return (DisasCond){
.c = TCG_COND_NE,
.a0 = cpu_psw_n,
.a0_is_n = true,
.a1 = NULL,
.a1_is_0 = true
};
}
static DisasCond cond_make_0_tmp(TCGCond c, TCGv_reg a0)
{
assert (c != TCG_COND_NEVER && c != TCG_COND_ALWAYS);
return (DisasCond){
.c = c, .a0 = a0, .a1_is_0 = true
};
}
static DisasCond cond_make_0(TCGCond c, TCGv_reg a0)
{
TCGv_reg tmp = tcg_temp_new();
tcg_gen_mov_reg(tmp, a0);
return cond_make_0_tmp(c, tmp);
}
static DisasCond cond_make(TCGCond c, TCGv_reg a0, TCGv_reg a1)
{
DisasCond r = { .c = c };
assert (c != TCG_COND_NEVER && c != TCG_COND_ALWAYS);
r.a0 = tcg_temp_new();
tcg_gen_mov_reg(r.a0, a0);
r.a1 = tcg_temp_new();
tcg_gen_mov_reg(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_reg(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;
cond->a0 = NULL;
cond->a1 = NULL;
/* fallthru */
case TCG_COND_ALWAYS:
cond->c = TCG_COND_NEVER;
break;
case TCG_COND_NEVER:
break;
}
}
static TCGv_reg get_temp(DisasContext *ctx)
{
unsigned i = ctx->ntempr++;
g_assert(i < ARRAY_SIZE(ctx->tempr));
return ctx->tempr[i] = tcg_temp_new();
}
#ifndef CONFIG_USER_ONLY
static TCGv_tl get_temp_tl(DisasContext *ctx)
{
unsigned i = ctx->ntempl++;
g_assert(i < ARRAY_SIZE(ctx->templ));
return ctx->templ[i] = tcg_temp_new_tl();
}
#endif
static TCGv_reg load_const(DisasContext *ctx, target_sreg v)
{
TCGv_reg t = get_temp(ctx);
tcg_gen_movi_reg(t, v);
return t;
}
static TCGv_reg load_gpr(DisasContext *ctx, unsigned reg)
{
if (reg == 0) {
TCGv_reg t = get_temp(ctx);
tcg_gen_movi_reg(t, 0);
return t;
} else {
return cpu_gr[reg];
}
}
static TCGv_reg 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_reg dest, TCGv_reg t)
{
if (ctx->null_cond.c != TCG_COND_NEVER) {
cond_prep(&ctx->null_cond);
tcg_gen_movcond_reg(ctx->null_cond.c, dest, ctx->null_cond.a0,
ctx->null_cond.a1, dest, t);
} else {
tcg_gen_mov_reg(dest, t);
}
}
static void save_gpr(DisasContext *ctx, unsigned reg, TCGv_reg 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]));
}
static void load_spr(DisasContext *ctx, TCGv_i64 dest, unsigned reg)
{
#ifdef CONFIG_USER_ONLY
tcg_gen_movi_i64(dest, 0);
#else
if (reg < 4) {
tcg_gen_mov_i64(dest, cpu_sr[reg]);
} else if (ctx->tb_flags & TB_FLAG_SR_SAME) {
tcg_gen_mov_i64(dest, cpu_srH);
} else {
tcg_gen_ld_i64(dest, cpu_env, offsetof(CPUHPPAState, sr[reg]));
}
#endif
}
/* 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_reg(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_reg(cpu_psw_n, 0);
}
tcg_gen_brcond_reg(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_reg(cpu_psw_n, 0);
}
return;
}
if (!ctx->null_cond.a0_is_n) {
cond_prep(&ctx->null_cond);
tcg_gen_setcond_reg(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_reg(cpu_psw_n, x);
}
}
/* Mark the end of an instruction that may have been nullified.
This is the pair to nullify_over. Always returns true so that
it may be tail-called from a translate function. */
static bool nullify_end(DisasContext *ctx)
{
TCGLabel *null_lab = ctx->null_lab;
DisasJumpType status = ctx->base.is_jmp;
/* For NEXT, NORETURN, STALE, we can easily continue (or exit).
For UPDATED, we cannot update on the nullified path. */
assert(status != DISAS_IAQ_N_UPDATED);
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 true;
}
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();
}
if (status == DISAS_NORETURN) {
ctx->base.is_jmp = DISAS_NEXT;
}
return true;
}
static void copy_iaoq_entry(TCGv_reg dest, target_ureg ival, TCGv_reg vval)
{
if (unlikely(ival == -1)) {
tcg_gen_mov_reg(dest, vval);
} else {
tcg_gen_movi_reg(dest, ival);
}
}
static inline target_ureg iaoq_dest(DisasContext *ctx, target_sreg 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 void 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);
ctx->base.is_jmp = DISAS_NORETURN;
}
static bool gen_excp_iir(DisasContext *ctx, int exc)
{
TCGv_reg tmp;
nullify_over(ctx);
tmp = tcg_const_reg(ctx->insn);
tcg_gen_st_reg(tmp, cpu_env, offsetof(CPUHPPAState, cr[CR_IIR]));
tcg_temp_free(tmp);
gen_excp(ctx, exc);
return nullify_end(ctx);
}
static bool gen_illegal(DisasContext *ctx)
{
return gen_excp_iir(ctx, EXCP_ILL);
}
#ifdef CONFIG_USER_ONLY
#define CHECK_MOST_PRIVILEGED(EXCP) \
return gen_excp_iir(ctx, EXCP)
#else
#define CHECK_MOST_PRIVILEGED(EXCP) \
do { \
if (ctx->privilege != 0) { \
return gen_excp_iir(ctx, EXCP); \
} \
} while (0)
#endif
static bool use_goto_tb(DisasContext *ctx, target_ureg dest)
{
/* Suppress goto_tb for page crossing, IO, or single-steping. */
return !(((ctx->base.pc_first ^ dest) & TARGET_PAGE_MASK)
|| (tb_cflags(ctx->base.tb) & CF_LAST_IO)
|| ctx->base.singlestep_enabled);
}
/* 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_ureg f, target_ureg b)
{
if (f != -1 && b != -1 && use_goto_tb(ctx, f)) {
tcg_gen_goto_tb(which);
tcg_gen_movi_reg(cpu_iaoq_f, f);
tcg_gen_movi_reg(cpu_iaoq_b, b);
tcg_gen_exit_tb(ctx->base.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->base.singlestep_enabled) {
gen_excp_1(EXCP_DEBUG);
} else {
tcg_gen_lookup_and_goto_ptr();
}
}
}
static bool cond_need_sv(int c)
{
return c == 2 || c == 3 || c == 6;
}
static bool cond_need_cb(int c)
{
return c == 4 || c == 5;
}
/*
* Compute conditional for arithmetic. See Page 5-3, Table 5-1, of
* the Parisc 1.1 Architecture Reference Manual for details.
*/
static DisasCond do_cond(unsigned cf, TCGv_reg res,
TCGv_reg cb_msb, TCGv_reg sv)
{
DisasCond cond;
TCGv_reg tmp;
switch (cf >> 1) {
case 0: /* Never / TR (0 / 1) */
cond = cond_make_f();
break;
case 1: /* = / <> (Z / !Z) */
cond = cond_make_0(TCG_COND_EQ, res);
break;
case 2: /* < / >= (N ^ V / !(N ^ V) */
tmp = tcg_temp_new();
tcg_gen_xor_reg(tmp, res, sv);
cond = cond_make_0_tmp(TCG_COND_LT, tmp);
break;
case 3: /* <= / > (N ^ V) | Z / !((N ^ V) | Z) */
/*
* Simplify:
* (N ^ V) | Z
* ((res < 0) ^ (sv < 0)) | !res
* ((res ^ sv) < 0) | !res
* (~(res ^ sv) >= 0) | !res
* !(~(res ^ sv) >> 31) | !res
* !(~(res ^ sv) >> 31 & res)
*/
tmp = tcg_temp_new();
tcg_gen_eqv_reg(tmp, res, sv);
tcg_gen_sari_reg(tmp, tmp, TARGET_REGISTER_BITS - 1);
tcg_gen_and_reg(tmp, tmp, res);
cond = cond_make_0_tmp(TCG_COND_EQ, tmp);
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_reg(tmp, cb_msb);
tcg_gen_and_reg(tmp, tmp, res);
cond = cond_make_0_tmp(TCG_COND_EQ, 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_reg(tmp, res, 1);
cond = cond_make_0_tmp(TCG_COND_NE, 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_reg res,
TCGv_reg in1, TCGv_reg in2, TCGv_reg 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, NULL, 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.
*
* Undefined or not, hardware does not trap. It seems reasonable to
* assume hardware treats cases c={4,5,6} as if C=0 & V=0, since that's
* how cases c={2,3} are treated.
*/
static DisasCond do_log_cond(unsigned cf, TCGv_reg res)
{
switch (cf) {
case 0: /* never */
case 9: /* undef, C */
case 11: /* undef, C & !Z */
case 12: /* undef, V */
return cond_make_f();
case 1: /* true */
case 8: /* undef, !C */
case 10: /* undef, !C | Z */
case 13: /* undef, !V */
return cond_make_t();
case 2: /* == */
return cond_make_0(TCG_COND_EQ, res);
case 3: /* <> */
return cond_make_0(TCG_COND_NE, res);
case 4: /* < */
return cond_make_0(TCG_COND_LT, res);
case 5: /* >= */
return cond_make_0(TCG_COND_GE, res);
case 6: /* <= */
return cond_make_0(TCG_COND_LE, res);
case 7: /* > */
return cond_make_0(TCG_COND_GT, res);
case 14: /* OD */
case 15: /* EV */
return do_cond(cf, res, NULL, NULL);
default:
g_assert_not_reached();
}
}
/* Similar, but for shift/extract/deposit conditions. */
static DisasCond do_sed_cond(unsigned orig, TCGv_reg 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_reg res,
TCGv_reg in1, TCGv_reg in2)
{
DisasCond cond;
TCGv_reg tmp, cb = NULL;
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_reg(cb, in1, in2);
tcg_gen_and_reg(tmp, in1, in2);
tcg_gen_andc_reg(cb, cb, res);
tcg_gen_or_reg(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_reg(tmp, res, 0x01010101u);
tcg_gen_andc_reg(tmp, tmp, res);
tcg_gen_andi_reg(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_reg(tmp, res, 0x00010001u);
tcg_gen_andc_reg(tmp, tmp, res);
tcg_gen_andi_reg(tmp, tmp, 0x80008000u);
cond = cond_make_0(TCG_COND_NE, tmp);
tcg_temp_free(tmp);
break;
case 4: /* SDC / NDC */
tcg_gen_andi_reg(cb, cb, 0x88888888u);
cond = cond_make_0(TCG_COND_NE, cb);
break;
case 6: /* SBC / NBC */
tcg_gen_andi_reg(cb, cb, 0x80808080u);
cond = cond_make_0(TCG_COND_NE, cb);
break;
case 7: /* SHC / NHC */
tcg_gen_andi_reg(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_reg do_add_sv(DisasContext *ctx, TCGv_reg res,
TCGv_reg in1, TCGv_reg in2)
{
TCGv_reg sv = get_temp(ctx);
TCGv_reg tmp = tcg_temp_new();
tcg_gen_xor_reg(sv, res, in1);
tcg_gen_xor_reg(tmp, in1, in2);
tcg_gen_andc_reg(sv, sv, tmp);
tcg_temp_free(tmp);
return sv;
}
/* Compute signed overflow for subtraction. */
static TCGv_reg do_sub_sv(DisasContext *ctx, TCGv_reg res,
TCGv_reg in1, TCGv_reg in2)
{
TCGv_reg sv = get_temp(ctx);
TCGv_reg tmp = tcg_temp_new();
tcg_gen_xor_reg(sv, res, in1);
tcg_gen_xor_reg(tmp, in1, in2);
tcg_gen_and_reg(sv, sv, tmp);
tcg_temp_free(tmp);
return sv;
}
static void do_add(DisasContext *ctx, unsigned rt, TCGv_reg in1,
TCGv_reg in2, unsigned shift, bool is_l,
bool is_tsv, bool is_tc, bool is_c, unsigned cf)
{
TCGv_reg dest, cb, cb_msb, sv, tmp;
unsigned c = cf >> 1;
DisasCond cond;
dest = tcg_temp_new();
cb = NULL;
cb_msb = NULL;
if (shift) {
tmp = get_temp(ctx);
tcg_gen_shli_reg(tmp, in1, shift);
in1 = tmp;
}
if (!is_l || cond_need_cb(c)) {
TCGv_reg zero = tcg_const_reg(0);
cb_msb = get_temp(ctx);
tcg_gen_add2_reg(dest, cb_msb, in1, zero, in2, zero);
if (is_c) {
tcg_gen_add2_reg(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_reg(cb, in1, in2);
tcg_gen_xor_reg(cb, cb, dest);
}
} else {
tcg_gen_add_reg(dest, in1, in2);
if (is_c) {
tcg_gen_add_reg(dest, dest, cpu_psw_cb_msb);
}
}
/* Compute signed overflow if required. */
sv = NULL;
if (is_tsv || cond_need_sv(c)) {
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_reg(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;
}
static bool do_add_reg(DisasContext *ctx, arg_rrr_cf_sh *a,
bool is_l, bool is_tsv, bool is_tc, bool is_c)
{
TCGv_reg tcg_r1, tcg_r2;
if (a->cf) {
nullify_over(ctx);
}
tcg_r1 = load_gpr(ctx, a->r1);
tcg_r2 = load_gpr(ctx, a->r2);
do_add(ctx, a->t, tcg_r1, tcg_r2, a->sh, is_l, is_tsv, is_tc, is_c, a->cf);
return nullify_end(ctx);
}
static bool do_add_imm(DisasContext *ctx, arg_rri_cf *a,
bool is_tsv, bool is_tc)
{
TCGv_reg tcg_im, tcg_r2;
if (a->cf) {
nullify_over(ctx);
}
tcg_im = load_const(ctx, a->i);
tcg_r2 = load_gpr(ctx, a->r);
do_add(ctx, a->t, tcg_im, tcg_r2, 0, 0, is_tsv, is_tc, 0, a->cf);
return nullify_end(ctx);
}
static void do_sub(DisasContext *ctx, unsigned rt, TCGv_reg in1,
TCGv_reg in2, bool is_tsv, bool is_b,
bool is_tc, unsigned cf)
{
TCGv_reg 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_reg(0);
if (is_b) {
/* DEST,C = IN1 + ~IN2 + C. */
tcg_gen_not_reg(cb, in2);
tcg_gen_add2_reg(dest, cb_msb, in1, zero, cpu_psw_cb_msb, zero);
tcg_gen_add2_reg(dest, cb_msb, dest, cb_msb, cb, zero);
tcg_gen_xor_reg(cb, cb, in1);
tcg_gen_xor_reg(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_reg(cb_msb, 1);
tcg_gen_sub2_reg(dest, cb_msb, in1, cb_msb, in2, zero);
tcg_gen_eqv_reg(cb, in1, in2);
tcg_gen_xor_reg(cb, cb, dest);
}
tcg_temp_free(zero);
/* Compute signed overflow if required. */
sv = NULL;
if (is_tsv || cond_need_sv(c)) {
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_reg(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;
}
static bool do_sub_reg(DisasContext *ctx, arg_rrr_cf *a,
bool is_tsv, bool is_b, bool is_tc)
{
TCGv_reg tcg_r1, tcg_r2;
if (a->cf) {
nullify_over(ctx);
}
tcg_r1 = load_gpr(ctx, a->r1);
tcg_r2 = load_gpr(ctx, a->r2);
do_sub(ctx, a->t, tcg_r1, tcg_r2, is_tsv, is_b, is_tc, a->cf);
return nullify_end(ctx);
}
static bool do_sub_imm(DisasContext *ctx, arg_rri_cf *a, bool is_tsv)
{
TCGv_reg tcg_im, tcg_r2;
if (a->cf) {
nullify_over(ctx);
}
tcg_im = load_const(ctx, a->i);
tcg_r2 = load_gpr(ctx, a->r);
do_sub(ctx, a->t, tcg_im, tcg_r2, is_tsv, 0, 0, a->cf);
return nullify_end(ctx);
}
static void do_cmpclr(DisasContext *ctx, unsigned rt, TCGv_reg in1,
TCGv_reg in2, unsigned cf)
{
TCGv_reg dest, sv;
DisasCond cond;
dest = tcg_temp_new();
tcg_gen_sub_reg(dest, in1, in2);
/* Compute signed overflow if required. */
sv = NULL;
if (cond_need_sv(cf >> 1)) {
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_reg(dest, 0);
save_gpr(ctx, rt, dest);
tcg_temp_free(dest);
/* Install the new nullification. */
cond_free(&ctx->null_cond);
ctx->null_cond = cond;
}
static void do_log(DisasContext *ctx, unsigned rt, TCGv_reg in1,
TCGv_reg in2, unsigned cf,
void (*fn)(TCGv_reg, TCGv_reg, TCGv_reg))
{
TCGv_reg 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);
}
}
static bool do_log_reg(DisasContext *ctx, arg_rrr_cf *a,
void (*fn)(TCGv_reg, TCGv_reg, TCGv_reg))
{
TCGv_reg tcg_r1, tcg_r2;
if (a->cf) {
nullify_over(ctx);
}
tcg_r1 = load_gpr(ctx, a->r1);
tcg_r2 = load_gpr(ctx, a->r2);
do_log(ctx, a->t, tcg_r1, tcg_r2, a->cf, fn);
return nullify_end(ctx);
}
static void do_unit(DisasContext *ctx, unsigned rt, TCGv_reg in1,
TCGv_reg in2, unsigned cf, bool is_tc,
void (*fn)(TCGv_reg, TCGv_reg, TCGv_reg))
{
TCGv_reg 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_reg tmp = tcg_temp_new();
cond_prep(&cond);
tcg_gen_setcond_reg(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;
}
}
#ifndef CONFIG_USER_ONLY
/* The "normal" usage is SP >= 0, wherein SP == 0 selects the space
from the top 2 bits of the base register. There are a few system
instructions that have a 3-bit space specifier, for which SR0 is
not special. To handle this, pass ~SP. */
static TCGv_i64 space_select(DisasContext *ctx, int sp, TCGv_reg base)
{
TCGv_ptr ptr;
TCGv_reg tmp;
TCGv_i64 spc;
if (sp != 0) {
if (sp < 0) {
sp = ~sp;
}
spc = get_temp_tl(ctx);
load_spr(ctx, spc, sp);
return spc;
}
if (ctx->tb_flags & TB_FLAG_SR_SAME) {
return cpu_srH;
}
ptr = tcg_temp_new_ptr();
tmp = tcg_temp_new();
spc = get_temp_tl(ctx);
tcg_gen_shri_reg(tmp, base, TARGET_REGISTER_BITS - 5);
tcg_gen_andi_reg(tmp, tmp, 030);
tcg_gen_trunc_reg_ptr(ptr, tmp);
tcg_temp_free(tmp);
tcg_gen_add_ptr(ptr, ptr, cpu_env);
tcg_gen_ld_i64(spc, ptr, offsetof(CPUHPPAState, sr[4]));
tcg_temp_free_ptr(ptr);
return spc;
}
#endif
static void form_gva(DisasContext *ctx, TCGv_tl *pgva, TCGv_reg *pofs,
unsigned rb, unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify, bool is_phys)
{
TCGv_reg base = load_gpr(ctx, rb);
TCGv_reg ofs;
/* Note that RX is mutually exclusive with DISP. */
if (rx) {
ofs = get_temp(ctx);
tcg_gen_shli_reg(ofs, cpu_gr[rx], scale);
tcg_gen_add_reg(ofs, ofs, base);
} else if (disp || modify) {
ofs = get_temp(ctx);
tcg_gen_addi_reg(ofs, base, disp);
} else {
ofs = base;
}
*pofs = ofs;
#ifdef CONFIG_USER_ONLY
*pgva = (modify <= 0 ? ofs : base);
#else
TCGv_tl addr = get_temp_tl(ctx);
tcg_gen_extu_reg_tl(addr, modify <= 0 ? ofs : base);
if (ctx->tb_flags & PSW_W) {
tcg_gen_andi_tl(addr, addr, 0x3fffffffffffffffull);
}
if (!is_phys) {
tcg_gen_or_tl(addr, addr, space_select(ctx, sp, base));
}
*pgva = addr;
#endif
}
/* 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_sreg disp,
unsigned sp, int modify, MemOp mop)
{
TCGv_reg ofs;
TCGv_tl addr;
/* Caller uses nullify_over/nullify_end. */
assert(ctx->null_cond.c == TCG_COND_NEVER);
form_gva(ctx, &addr, &ofs, rb, rx, scale, disp, sp, modify,
ctx->mmu_idx == MMU_PHYS_IDX);
tcg_gen_qemu_ld_reg(dest, addr, ctx->mmu_idx, mop);
if (modify) {
save_gpr(ctx, rb, ofs);
}
}
static void do_load_64(DisasContext *ctx, TCGv_i64 dest, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify, MemOp mop)
{
TCGv_reg ofs;
TCGv_tl addr;
/* Caller uses nullify_over/nullify_end. */
assert(ctx->null_cond.c == TCG_COND_NEVER);
form_gva(ctx, &addr, &ofs, rb, rx, scale, disp, sp, modify,
ctx->mmu_idx == MMU_PHYS_IDX);
tcg_gen_qemu_ld_i64(dest, addr, ctx->mmu_idx, mop);
if (modify) {
save_gpr(ctx, rb, ofs);
}
}
static void do_store_32(DisasContext *ctx, TCGv_i32 src, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify, MemOp mop)
{
TCGv_reg ofs;
TCGv_tl addr;
/* Caller uses nullify_over/nullify_end. */
assert(ctx->null_cond.c == TCG_COND_NEVER);
form_gva(ctx, &addr, &ofs, rb, rx, scale, disp, sp, modify,
ctx->mmu_idx == MMU_PHYS_IDX);
tcg_gen_qemu_st_i32(src, addr, ctx->mmu_idx, mop);
if (modify) {
save_gpr(ctx, rb, ofs);
}
}
static void do_store_64(DisasContext *ctx, TCGv_i64 src, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify, MemOp mop)
{
TCGv_reg ofs;
TCGv_tl addr;
/* Caller uses nullify_over/nullify_end. */
assert(ctx->null_cond.c == TCG_COND_NEVER);
form_gva(ctx, &addr, &ofs, rb, rx, scale, disp, sp, modify,
ctx->mmu_idx == MMU_PHYS_IDX);
tcg_gen_qemu_st_i64(src, addr, ctx->mmu_idx, mop);
if (modify) {
save_gpr(ctx, rb, ofs);
}
}
#if TARGET_REGISTER_BITS == 64
#define do_load_reg do_load_64
#define do_store_reg do_store_64
#else
#define do_load_reg do_load_32
#define do_store_reg do_store_32
#endif
static bool do_load(DisasContext *ctx, unsigned rt, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify, MemOp mop)
{
TCGv_reg 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_reg(ctx, dest, rb, rx, scale, disp, sp, modify, mop);
save_gpr(ctx, rt, dest);
return nullify_end(ctx);
}
static bool do_floadw(DisasContext *ctx, unsigned rt, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify)
{
TCGv_i32 tmp;
nullify_over(ctx);
tmp = tcg_temp_new_i32();
do_load_32(ctx, tmp, rb, rx, scale, disp, sp, 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);
}
static bool trans_fldw(DisasContext *ctx, arg_ldst *a)
{
return do_floadw(ctx, a->t, a->b, a->x, a->scale ? 2 : 0,
a->disp, a->sp, a->m);
}
static bool do_floadd(DisasContext *ctx, unsigned rt, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify)
{
TCGv_i64 tmp;
nullify_over(ctx);
tmp = tcg_temp_new_i64();
do_load_64(ctx, tmp, rb, rx, scale, disp, sp, 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);
}
static bool trans_fldd(DisasContext *ctx, arg_ldst *a)
{
return do_floadd(ctx, a->t, a->b, a->x, a->scale ? 3 : 0,
a->disp, a->sp, a->m);
}
static bool do_store(DisasContext *ctx, unsigned rt, unsigned rb,
target_sreg disp, unsigned sp,
int modify, MemOp mop)
{
nullify_over(ctx);
do_store_reg(ctx, load_gpr(ctx, rt), rb, 0, 0, disp, sp, modify, mop);
return nullify_end(ctx);
}
static bool do_fstorew(DisasContext *ctx, unsigned rt, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify)
{
TCGv_i32 tmp;
nullify_over(ctx);
tmp = load_frw_i32(rt);
do_store_32(ctx, tmp, rb, rx, scale, disp, sp, modify, MO_TEUL);
tcg_temp_free_i32(tmp);
return nullify_end(ctx);
}
static bool trans_fstw(DisasContext *ctx, arg_ldst *a)
{
return do_fstorew(ctx, a->t, a->b, a->x, a->scale ? 2 : 0,
a->disp, a->sp, a->m);
}
static bool do_fstored(DisasContext *ctx, unsigned rt, unsigned rb,
unsigned rx, int scale, target_sreg disp,
unsigned sp, int modify)
{
TCGv_i64 tmp;
nullify_over(ctx);
tmp = load_frd(rt);
do_store_64(ctx, tmp, rb, rx, scale, disp, sp, modify, MO_TEQ);
tcg_temp_free_i64(tmp);
return nullify_end(ctx);
}
static bool trans_fstd(DisasContext *ctx, arg_ldst *a)
{
return do_fstored(ctx, a->t, a->b, a->x, a->scale ? 3 : 0,
a->disp, a->sp, a->m);
}
static bool 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);
}
static bool 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);
}
static bool 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);
}
static bool 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);
}
static bool 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);
}
static bool 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);
}
/* Emit an unconditional branch to a direct target, which may or may not
have already had nullification handled. */
static bool do_dbranch(DisasContext *ctx, target_ureg 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;
}
} 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);
nullify_set(ctx, 0);
gen_goto_tb(ctx, 1, ctx->iaoq_b, ctx->iaoq_n);
ctx->base.is_jmp = DISAS_NORETURN;
}
return true;
}
/* Emit a conditional branch to a direct target. If the branch itself
is nullified, we should have already used nullify_over. */
static bool do_cbranch(DisasContext *ctx, target_sreg disp, bool is_n,
DisasCond *cond)
{
target_ureg 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_reg(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);
if (ctx->iaoq_n == -1) {
/* The temporary iaoq_n_var died at the branch above.
Regenerate it here instead of saving it. */
tcg_gen_addi_reg(ctx->iaoq_n_var, cpu_iaoq_b, 4);
}
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;
ctx->base.is_jmp = DISAS_IAQ_N_STALE;
} else {
ctx->base.is_jmp = DISAS_NORETURN;
}
return true;
}
/* Emit an unconditional branch to an indirect target. This handles
nullification of the branch itself. */
static bool do_ibranch(DisasContext *ctx, TCGv_reg dest,
unsigned link, bool is_n)
{
TCGv_reg 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_reg(next, dest);
if (is_n) {
if (use_nullify_skip(ctx)) {
tcg_gen_mov_reg(cpu_iaoq_f, next);
tcg_gen_addi_reg(cpu_iaoq_b, next, 4);
nullify_set(ctx, 0);
ctx->base.is_jmp = DISAS_IAQ_N_UPDATED;
return true;
}
ctx->null_cond.c = TCG_COND_ALWAYS;
}
ctx->iaoq_n = -1;
ctx->iaoq_n_var = next;
} 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_reg(cpu_iaoq_f, dest);
tcg_gen_addi_reg(cpu_iaoq_b, dest, 4);
nullify_over(ctx);
if (link != 0) {
tcg_gen_movi_reg(cpu_gr[link], ctx->iaoq_n);
}
tcg_gen_lookup_and_goto_ptr();
return nullify_end(ctx);
} 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_reg(c, next, a0, a1, tmp, dest);
ctx->iaoq_n = -1;
ctx->iaoq_n_var = next;
if (link != 0) {
tcg_gen_movcond_reg(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_reg(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 true;
}
/* Implement
* if (IAOQ_Front{30..31} < GR[b]{30..31})
* IAOQ_Next{30..31} ← GR[b]{30..31};
* else
* IAOQ_Next{30..31} ← IAOQ_Front{30..31};
* which keeps the privilege level from being increased.
*/
static TCGv_reg do_ibranch_priv(DisasContext *ctx, TCGv_reg offset)
{
TCGv_reg dest;
switch (ctx->privilege) {
case 0:
/* Privilege 0 is maximum and is allowed to decrease. */
return offset;
case 3:
/* Privilege 3 is minimum and is never allowed to increase. */
dest = get_temp(ctx);
tcg_gen_ori_reg(dest, offset, 3);
break;
default:
dest = get_temp(ctx);
tcg_gen_andi_reg(dest, offset, -4);
tcg_gen_ori_reg(dest, dest, ctx->privilege);
tcg_gen_movcond_reg(TCG_COND_GTU, dest, dest, offset, dest, offset);
break;
}
return dest;
}
#ifdef CONFIG_USER_ONLY
/* 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 void 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_reg(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 & -4) {
case 0x00: /* Null pointer call */
gen_excp_1(EXCP_IMP);
ctx->base.is_jmp = DISAS_NORETURN;
break;
case 0xb0: /* LWS */
gen_excp_1(EXCP_SYSCALL_LWS);
ctx->base.is_jmp = DISAS_NORETURN;
break;
case 0xe0: /* SET_THREAD_POINTER */
tcg_gen_st_reg(cpu_gr[26], cpu_env, offsetof(CPUHPPAState, cr[27]));
tcg_gen_ori_reg(cpu_iaoq_f, cpu_gr[31], 3);
tcg_gen_addi_reg(cpu_iaoq_b, cpu_iaoq_f, 4);
ctx->base.is_jmp = DISAS_IAQ_N_UPDATED;
break;
case 0x100: /* SYSCALL */
gen_excp_1(EXCP_SYSCALL);
ctx->base.is_jmp = DISAS_NORETURN;
break;
default:
do_sigill:
gen_excp_1(EXCP_ILL);
ctx->base.is_jmp = DISAS_NORETURN;
break;
}
}
#endif
static bool trans_nop(DisasContext *ctx, arg_nop *a)
{
cond_free(&ctx->null_cond);
return true;
}
static bool trans_break(DisasContext *ctx, arg_break *a)
{
return gen_excp_iir(ctx, EXCP_BREAK);
}
static bool trans_sync(DisasContext *ctx, arg_sync *a)
{
/* No point in nullifying the memory barrier. */
tcg_gen_mb(TCG_BAR_SC | TCG_MO_ALL);
cond_free(&ctx->null_cond);
return true;
}
static bool trans_mfia(DisasContext *ctx, arg_mfia *a)
{
unsigned rt = a->t;
TCGv_reg tmp = dest_gpr(ctx, rt);
tcg_gen_movi_reg(tmp, ctx->iaoq_f);
save_gpr(ctx, rt, tmp);
cond_free(&ctx->null_cond);
return true;
}
static bool trans_mfsp(DisasContext *ctx, arg_mfsp *a)
{
unsigned rt = a->t;
unsigned rs = a->sp;
TCGv_i64 t0 = tcg_temp_new_i64();
TCGv_reg t1 = tcg_temp_new();
load_spr(ctx, t0, rs);
tcg_gen_shri_i64(t0, t0, 32);
tcg_gen_trunc_i64_reg(t1, t0);
save_gpr(ctx, rt, t1);
tcg_temp_free(t1);
tcg_temp_free_i64(t0);
cond_free(&ctx->null_cond);
return true;
}
static bool trans_mfctl(DisasContext *ctx, arg_mfctl *a)
{
unsigned rt = a->t;
unsigned ctl = a->r;
TCGv_reg tmp;
switch (ctl) {
case CR_SAR:
#ifdef TARGET_HPPA64
if (a->e == 0) {
/* MFSAR without ,W masks low 5 bits. */
tmp = dest_gpr(ctx, rt);
tcg_gen_andi_reg(tmp, cpu_sar, 31);
save_gpr(ctx, rt, tmp);
goto done;
}
#endif
save_gpr(ctx, rt, cpu_sar);
goto done;
case CR_IT: /* Interval Timer */
/* FIXME: Respect PSW_S bit. */
nullify_over(ctx);
tmp = dest_gpr(ctx, rt);
if (tb_cflags(ctx->base.tb) & CF_USE_ICOUNT) {
gen_io_start();
gen_helper_read_interval_timer(tmp);
ctx->base.is_jmp = DISAS_IAQ_N_STALE;
} else {
gen_helper_read_interval_timer(tmp);
}
save_gpr(ctx, rt, tmp);
return nullify_end(ctx);
case 26:
case 27:
break;
default:
/* All other control registers are privileged. */
CHECK_MOST_PRIVILEGED(EXCP_PRIV_REG);
break;
}
tmp = get_temp(ctx);
tcg_gen_ld_reg(tmp, cpu_env, offsetof(CPUHPPAState, cr[ctl]));
save_gpr(ctx, rt, tmp);
done:
cond_free(&ctx->null_cond);
return true;
}
static bool trans_mtsp(DisasContext *ctx, arg_mtsp *a)
{
unsigned rr = a->r;
unsigned rs = a->sp;
TCGv_i64 t64;
if (rs >= 5) {
CHECK_MOST_PRIVILEGED(EXCP_PRIV_REG);
}
nullify_over(ctx);
t64 = tcg_temp_new_i64();
tcg_gen_extu_reg_i64(t64, load_gpr(ctx, rr));
tcg_gen_shli_i64(t64, t64, 32);
if (rs >= 4) {
tcg_gen_st_i64(t64, cpu_env, offsetof(CPUHPPAState, sr[rs]));
ctx->tb_flags &= ~TB_FLAG_SR_SAME;
} else {
tcg_gen_mov_i64(cpu_sr[rs], t64);
}
tcg_temp_free_i64(t64);
return nullify_end(ctx);
}
static bool trans_mtctl(DisasContext *ctx, arg_mtctl *a)
{
unsigned ctl = a->t;
TCGv_reg reg;
TCGv_reg tmp;
if (ctl == CR_SAR) {
reg = load_gpr(ctx, a->r);
tmp = tcg_temp_new();
tcg_gen_andi_reg(tmp, reg, TARGET_REGISTER_BITS - 1);
save_or_nullify(ctx, cpu_sar, tmp);
tcg_temp_free(tmp);
cond_free(&ctx->null_cond);
return true;
}
/* All other control registers are privileged or read-only. */
CHECK_MOST_PRIVILEGED(EXCP_PRIV_REG);
#ifndef CONFIG_USER_ONLY
nullify_over(ctx);
reg = load_gpr(ctx, a->r);
switch (ctl) {
case CR_IT:
gen_helper_write_interval_timer(cpu_env, reg);
break;
case CR_EIRR:
gen_helper_write_eirr(cpu_env, reg);
break;
case CR_EIEM:
gen_helper_write_eiem(cpu_env, reg);
ctx->base.is_jmp = DISAS_IAQ_N_STALE_EXIT;
break;
case CR_IIASQ:
case CR_IIAOQ:
/* FIXME: Respect PSW_Q bit */
/* The write advances the queue and stores to the back element. */
tmp = get_temp(ctx);
tcg_gen_ld_reg(tmp, cpu_env,
offsetof(CPUHPPAState, cr_back[ctl - CR_IIASQ]));
tcg_gen_st_reg(tmp, cpu_env, offsetof(CPUHPPAState, cr[ctl]));
tcg_gen_st_reg(reg, cpu_env,
offsetof(CPUHPPAState, cr_back[ctl - CR_IIASQ]));
break;
case CR_PID1:
case CR_PID2:
case CR_PID3:
case CR_PID4:
tcg_gen_st_reg(reg, cpu_env, offsetof(CPUHPPAState, cr[ctl]));
#ifndef CONFIG_USER_ONLY
gen_helper_change_prot_id(cpu_env);
#endif
break;
default:
tcg_gen_st_reg(reg, cpu_env, offsetof(CPUHPPAState, cr[ctl]));
break;
}
return nullify_end(ctx);
#endif
}
static bool trans_mtsarcm(DisasContext *ctx, arg_mtsarcm *a)
{
TCGv_reg tmp = tcg_temp_new();
tcg_gen_not_reg(tmp, load_gpr(ctx, a->r));
tcg_gen_andi_reg(tmp, tmp, TARGET_REGISTER_BITS - 1);
save_or_nullify(ctx, cpu_sar, tmp);
tcg_temp_free(tmp);
cond_free(&ctx->null_cond);
return true;
}
static bool trans_ldsid(DisasContext *ctx, arg_ldsid *a)
{
TCGv_reg dest = dest_gpr(ctx, a->t);
#ifdef CONFIG_USER_ONLY
/* We don't implement space registers in user mode. */
tcg_gen_movi_reg(dest, 0);
#else
TCGv_i64 t0 = tcg_temp_new_i64();
tcg_gen_mov_i64(t0, space_select(ctx, a->sp, load_gpr(ctx, a->b)));
tcg_gen_shri_i64(t0, t0, 32);
tcg_gen_trunc_i64_reg(dest, t0);
tcg_temp_free_i64(t0);
#endif
save_gpr(ctx, a->t, dest);
cond_free(&ctx->null_cond);
return true;
}
static bool trans_rsm(DisasContext *ctx, arg_rsm *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_reg tmp;
nullify_over(ctx);
tmp = get_temp(ctx);
tcg_gen_ld_reg(tmp, cpu_env, offsetof(CPUHPPAState, psw));
tcg_gen_andi_reg(tmp, tmp, ~a->i);
gen_helper_swap_system_mask(tmp, cpu_env, tmp);
save_gpr(ctx, a->t, tmp);
/* Exit the TB to recognize new interrupts, e.g. PSW_M. */
ctx->base.is_jmp = DISAS_IAQ_N_STALE_EXIT;
return nullify_end(ctx);
#endif
}
static bool trans_ssm(DisasContext *ctx, arg_ssm *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_reg tmp;
nullify_over(ctx);
tmp = get_temp(ctx);
tcg_gen_ld_reg(tmp, cpu_env, offsetof(CPUHPPAState, psw));
tcg_gen_ori_reg(tmp, tmp, a->i);
gen_helper_swap_system_mask(tmp, cpu_env, tmp);
save_gpr(ctx, a->t, tmp);
/* Exit the TB to recognize new interrupts, e.g. PSW_I. */
ctx->base.is_jmp = DISAS_IAQ_N_STALE_EXIT;
return nullify_end(ctx);
#endif
}
static bool trans_mtsm(DisasContext *ctx, arg_mtsm *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_reg tmp, reg;
nullify_over(ctx);
reg = load_gpr(ctx, a->r);
tmp = get_temp(ctx);
gen_helper_swap_system_mask(tmp, cpu_env, reg);
/* Exit the TB to recognize new interrupts. */
ctx->base.is_jmp = DISAS_IAQ_N_STALE_EXIT;
return nullify_end(ctx);
#endif
}
static bool do_rfi(DisasContext *ctx, bool rfi_r)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
nullify_over(ctx);
if (rfi_r) {
gen_helper_rfi_r(cpu_env);
} else {
gen_helper_rfi(cpu_env);
}
/* Exit the TB to recognize new interrupts. */
if (ctx->base.singlestep_enabled) {
gen_excp_1(EXCP_DEBUG);
} else {
tcg_gen_exit_tb(NULL, 0);
}
ctx->base.is_jmp = DISAS_NORETURN;
return nullify_end(ctx);
#endif
}
static bool trans_rfi(DisasContext *ctx, arg_rfi *a)
{
return do_rfi(ctx, false);
}
static bool trans_rfi_r(DisasContext *ctx, arg_rfi_r *a)
{
return do_rfi(ctx, true);
}
static bool trans_halt(DisasContext *ctx, arg_halt *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
nullify_over(ctx);
gen_helper_halt(cpu_env);
ctx->base.is_jmp = DISAS_NORETURN;
return nullify_end(ctx);
#endif
}
static bool trans_reset(DisasContext *ctx, arg_reset *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
nullify_over(ctx);
gen_helper_reset(cpu_env);
ctx->base.is_jmp = DISAS_NORETURN;
return nullify_end(ctx);
#endif
}
static bool trans_nop_addrx(DisasContext *ctx, arg_ldst *a)
{
if (a->m) {
TCGv_reg dest = dest_gpr(ctx, a->b);
TCGv_reg src1 = load_gpr(ctx, a->b);
TCGv_reg src2 = load_gpr(ctx, a->x);
/* The only thing we need to do is the base register modification. */
tcg_gen_add_reg(dest, src1, src2);
save_gpr(ctx, a->b, dest);
}
cond_free(&ctx->null_cond);
return true;
}
static bool trans_probe(DisasContext *ctx, arg_probe *a)
{
TCGv_reg dest, ofs;
TCGv_i32 level, want;
TCGv_tl addr;
nullify_over(ctx);
dest = dest_gpr(ctx, a->t);
form_gva(ctx, &addr, &ofs, a->b, 0, 0, 0, a->sp, 0, false);
if (a->imm) {
level = tcg_const_i32(a->ri);
} else {
level = tcg_temp_new_i32();
tcg_gen_trunc_reg_i32(level, load_gpr(ctx, a->ri));
tcg_gen_andi_i32(level, level, 3);
}
want = tcg_const_i32(a->write ? PAGE_WRITE : PAGE_READ);
gen_helper_probe(dest, cpu_env, addr, level, want);
tcg_temp_free_i32(want);
tcg_temp_free_i32(level);
save_gpr(ctx, a->t, dest);
return nullify_end(ctx);
}
static bool trans_ixtlbx(DisasContext *ctx, arg_ixtlbx *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_tl addr;
TCGv_reg ofs, reg;
nullify_over(ctx);
form_gva(ctx, &addr, &ofs, a->b, 0, 0, 0, a->sp, 0, false);
reg = load_gpr(ctx, a->r);
if (a->addr) {
gen_helper_itlba(cpu_env, addr, reg);
} else {
gen_helper_itlbp(cpu_env, addr, reg);
}
/* Exit TB for TLB change if mmu is enabled. */
if (ctx->tb_flags & PSW_C) {
ctx->base.is_jmp = DISAS_IAQ_N_STALE;
}
return nullify_end(ctx);
#endif
}
static bool trans_pxtlbx(DisasContext *ctx, arg_pxtlbx *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_tl addr;
TCGv_reg ofs;
nullify_over(ctx);
form_gva(ctx, &addr, &ofs, a->b, a->x, 0, 0, a->sp, a->m, false);
if (a->m) {
save_gpr(ctx, a->b, ofs);
}
if (a->local) {
gen_helper_ptlbe(cpu_env);
} else {
gen_helper_ptlb(cpu_env, addr);
}
/* Exit TB for TLB change if mmu is enabled. */
if (ctx->tb_flags & PSW_C) {
ctx->base.is_jmp = DISAS_IAQ_N_STALE;
}
return nullify_end(ctx);
#endif
}
/*
* Implement the pcxl and pcxl2 Fast TLB Insert instructions.
* See
* https://parisc.wiki.kernel.org/images-parisc/a/a9/Pcxl2_ers.pdf
* page 13-9 (195/206)
*/
static bool trans_ixtlbxf(DisasContext *ctx, arg_ixtlbxf *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_tl addr, atl, stl;
TCGv_reg reg;
nullify_over(ctx);
/*
* FIXME:
* if (not (pcxl or pcxl2))
* return gen_illegal(ctx);
*
* Note for future: these are 32-bit systems; no hppa64.
*/
atl = tcg_temp_new_tl();
stl = tcg_temp_new_tl();
addr = tcg_temp_new_tl();
tcg_gen_ld32u_i64(stl, cpu_env,
a->data ? offsetof(CPUHPPAState, cr[CR_ISR])
: offsetof(CPUHPPAState, cr[CR_IIASQ]));
tcg_gen_ld32u_i64(atl, cpu_env,
a->data ? offsetof(CPUHPPAState, cr[CR_IOR])
: offsetof(CPUHPPAState, cr[CR_IIAOQ]));
tcg_gen_shli_i64(stl, stl, 32);
tcg_gen_or_tl(addr, atl, stl);
tcg_temp_free_tl(atl);
tcg_temp_free_tl(stl);
reg = load_gpr(ctx, a->r);
if (a->addr) {
gen_helper_itlba(cpu_env, addr, reg);
} else {
gen_helper_itlbp(cpu_env, addr, reg);
}
tcg_temp_free_tl(addr);
/* Exit TB for TLB change if mmu is enabled. */
if (ctx->tb_flags & PSW_C) {
ctx->base.is_jmp = DISAS_IAQ_N_STALE;
}
return nullify_end(ctx);
#endif
}
static bool trans_lpa(DisasContext *ctx, arg_ldst *a)
{
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
#ifndef CONFIG_USER_ONLY
TCGv_tl vaddr;
TCGv_reg ofs, paddr;
nullify_over(ctx);
form_gva(ctx, &vaddr, &ofs, a->b, a->x, 0, 0, a->sp, a->m, false);
paddr = tcg_temp_new();
gen_helper_lpa(paddr, cpu_env, vaddr);
/* Note that physical address result overrides base modification. */
if (a->m) {
save_gpr(ctx, a->b, ofs);
}
save_gpr(ctx, a->t, paddr);
tcg_temp_free(paddr);
return nullify_end(ctx);
#endif
}
static bool trans_lci(DisasContext *ctx, arg_lci *a)
{
TCGv_reg ci;
CHECK_MOST_PRIVILEGED(EXCP_PRIV_OPR);
/* The Coherence Index is an implementation-defined function of the
physical address. Two addresses with the same CI have a coherent
view of the cache. Our implementation is to return 0 for all,
since the entire address space is coherent. */
ci = tcg_const_reg(0);
save_gpr(ctx, a->t, ci);
tcg_temp_free(ci);
cond_free(&ctx->null_cond);
return true;
}
static bool trans_add(DisasContext *ctx, arg_rrr_cf_sh *a)
{
return do_add_reg(ctx, a, false, false, false, false);
}
static bool trans_add_l(DisasContext *ctx, arg_rrr_cf_sh *a)
{
return do_add_reg(ctx, a, true, false, false, false);
}
static bool trans_add_tsv(DisasContext *ctx, arg_rrr_cf_sh *a)
{
return do_add_reg(ctx, a, false, true, false, false);
}
static bool trans_add_c(DisasContext *ctx, arg_rrr_cf_sh *a)
{
return do_add_reg(ctx, a, false, false, false, true);
}
static bool trans_add_c_tsv(DisasContext *ctx, arg_rrr_cf_sh *a)
{
return do_add_reg(ctx, a, false, true, false, true);
}
static bool trans_sub(DisasContext *ctx, arg_rrr_cf *a)
{
return do_sub_reg(ctx, a, false, false, false);
}
static bool trans_sub_tsv(DisasContext *ctx, arg_rrr_cf *a)
{
return do_sub_reg(ctx, a, true, false, false);
}
static bool trans_sub_tc(DisasContext *ctx, arg_rrr_cf *a)
{
return do_sub_reg(ctx, a, false, false, true);
}
static bool trans_sub_tsv_tc(DisasContext *ctx, arg_rrr_cf *a)
{
return do_sub_reg(ctx, a, true, false, true);
}
static bool trans_sub_b(DisasContext *ctx, arg_rrr_cf *a)
{
return do_sub_reg(ctx, a, false, true, false);
}
static bool trans_sub_b_tsv(DisasContext *ctx, arg_rrr_cf *a)
{
return do_sub_reg(ctx, a, true, true, false);
}
static bool trans_andcm(DisasContext *ctx, arg_rrr_cf *a)
{
return do_log_reg(ctx, a, tcg_gen_andc_reg);
}
static bool trans_and(DisasContext *ctx, arg_rrr_cf *a)
{
return do_log_reg(ctx, a, tcg_gen_and_reg);
}
static bool trans_or(DisasContext *ctx, arg_rrr_cf *a)
{
if (a->cf == 0) {
unsigned r2 = a->r2;
unsigned r1 = a->r1;
unsigned rt = a->t;
if (rt == 0) { /* NOP */
cond_free(&ctx->null_cond);
return true;
}
if (r2 == 0) { /* COPY */
if (r1 == 0) {
TCGv_reg dest = dest_gpr(ctx, rt);
tcg_gen_movi_reg(dest, 0);
save_gpr(ctx, rt, dest);
} else {
save_gpr(ctx, rt, cpu_gr[r1]);
}
cond_free(&ctx->null_cond);
return true;
}
#ifndef CONFIG_USER_ONLY
/* These are QEMU extensions and are nops in the real architecture:
*
* or %r10,%r10,%r10 -- idle loop; wait for interrupt
* or %r31,%r31,%r31 -- death loop; offline cpu
* currently implemented as idle.
*/
if ((rt == 10 || rt == 31) && r1 == rt && r2 == rt) { /* PAUSE */
TCGv_i32 tmp;
/* No need to check for supervisor, as userland can only pause
until the next timer interrupt. */
nullify_over(ctx);
/* Advance the instruction queue. */
copy_iaoq_entry(cpu_iaoq_f, ctx->iaoq_b, cpu_iaoq_b);
copy_iaoq_entry(cpu_iaoq_b, ctx->iaoq_n, ctx->iaoq_n_var);
nullify_set(ctx, 0);
/* Tell the qemu main loop to halt until this cpu has work. */
tmp = tcg_const_i32(1);
tcg_gen_st_i32(tmp, cpu_env, -offsetof(HPPACPU, env) +
offsetof(CPUState, halted));
tcg_temp_free_i32(tmp);
gen_excp_1(EXCP_HALTED);
ctx->base.is_jmp = DISAS_NORETURN;
return nullify_end(ctx);
}
#endif
}
return do_log_reg(ctx, a, tcg_gen_or_reg);
}
static bool trans_xor(DisasContext *ctx, arg_rrr_cf *a)
{
return do_log_reg(ctx, a, tcg_gen_xor_reg);
}
static bool trans_cmpclr(DisasContext *ctx, arg_rrr_cf *a)
{
TCGv_reg tcg_r1, tcg_r2;
if (a->cf) {
nullify_over(ctx);
}
tcg_r1