/*
* Alpha emulation cpu translation for qemu.
*
* Copyright (c) 2007 Jocelyn Mayer
*
* 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 "cpu.h"
#include "disas/disas.h"
#include "qemu/host-utils.h"
#include "tcg-op.h"
#include "exec/cpu_ldst.h"
#include "exec/helper-proto.h"
#include "exec/helper-gen.h"
#include "trace-tcg.h"
#undef ALPHA_DEBUG_DISAS
#define CONFIG_SOFTFLOAT_INLINE
#ifdef ALPHA_DEBUG_DISAS
# define LOG_DISAS(...) qemu_log_mask(CPU_LOG_TB_IN_ASM, ## __VA_ARGS__)
#else
# define LOG_DISAS(...) do { } while (0)
#endif
typedef struct DisasContext DisasContext;
struct DisasContext {
struct TranslationBlock *tb;
uint64_t pc;
int mem_idx;
/* Current rounding mode for this TB. */
int tb_rm;
/* Current flush-to-zero setting for this TB. */
int tb_ftz;
/* implver value for this CPU. */
int implver;
/* Temporaries for $31 and $f31 as source and destination. */
TCGv zero;
TCGv sink;
/* Temporary for immediate constants. */
TCGv lit;
bool singlestep_enabled;
};
/* 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 PC (for whatever reason), so there's no need to do it again on
exiting the TB. */
EXIT_PC_UPDATED,
/* We are exiting the TB, but have neither emitted a goto_tb, nor
updated the PC for the next instruction to be executed. */
EXIT_PC_STALE,
/* We are ending the TB with a noreturn function call, e.g. longjmp.
No following code will be executed. */
EXIT_NORETURN,
} ExitStatus;
/* global register indexes */
static TCGv_ptr cpu_env;
static TCGv cpu_ir[31];
static TCGv cpu_fir[31];
static TCGv cpu_pc;
static TCGv cpu_lock_addr;
static TCGv cpu_lock_st_addr;
static TCGv cpu_lock_value;
#include "exec/gen-icount.h"
void alpha_translate_init(void)
{
#define DEF_VAR(V) { &cpu_##V, #V, offsetof(CPUAlphaState, V) }
typedef struct { TCGv *var; const char *name; int ofs; } GlobalVar;
static const GlobalVar vars[] = {
DEF_VAR(pc),
DEF_VAR(lock_addr),
DEF_VAR(lock_st_addr),
DEF_VAR(lock_value),
};
#undef DEF_VAR
/* Use the symbolic register names that match the disassembler. */
static const char greg_names[31][4] = {
"v0", "t0", "t1", "t2", "t3", "t4", "t5", "t6",
"t7", "s0", "s1", "s2", "s3", "s4", "s5", "fp",
"a0", "a1", "a2", "a3", "a4", "a5", "t8", "t9",
"t10", "t11", "ra", "t12", "at", "gp", "sp"
};
static const char freg_names[31][4] = {
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
"f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
"f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
"f24", "f25", "f26", "f27", "f28", "f29", "f30"
};
static bool done_init = 0;
int i;
if (done_init) {
return;
}
done_init = 1;
cpu_env = tcg_global_reg_new_ptr(TCG_AREG0, "env");
for (i = 0; i < 31; i++) {
cpu_ir[i] = tcg_global_mem_new_i64(TCG_AREG0,
offsetof(CPUAlphaState, ir[i]),
greg_names[i]);
}
for (i = 0; i < 31; i++) {
cpu_fir[i] = tcg_global_mem_new_i64(TCG_AREG0,
offsetof(CPUAlphaState, fir[i]),
freg_names[i]);
}
for (i = 0; i < ARRAY_SIZE(vars); ++i) {
const GlobalVar *v = &vars[i];
*v->var = tcg_global_mem_new_i64(TCG_AREG0, v->ofs, v->name);
}
}
static TCGv load_zero(DisasContext *ctx)
{
if (TCGV_IS_UNUSED_I64(ctx->zero)) {
ctx->zero = tcg_const_i64(0);
}
return ctx->zero;
}
static TCGv dest_sink(DisasContext *ctx)
{
if (TCGV_IS_UNUSED_I64(ctx->sink)) {
ctx->sink = tcg_temp_new();
}
return ctx->sink;
}
static TCGv load_gpr(DisasContext *ctx, unsigned reg)
{
if (likely(reg < 31)) {
return cpu_ir[reg];
} else {
return load_zero(ctx);
}
}
static TCGv load_gpr_lit(DisasContext *ctx, unsigned reg,
uint8_t lit, bool islit)
{
if (islit) {
ctx->lit = tcg_const_i64(lit);
return ctx->lit;
} else if (likely(reg < 31)) {
return cpu_ir[reg];
} else {
return load_zero(ctx);
}
}
static TCGv dest_gpr(DisasContext *ctx, unsigned reg)
{
if (likely(reg < 31)) {
return cpu_ir[reg];
} else {
return dest_sink(ctx);
}
}
static TCGv load_fpr(DisasContext *ctx, unsigned reg)
{
if (likely(reg < 31)) {
return cpu_fir[reg];
} else {
return load_zero(ctx);
}
}
static TCGv dest_fpr(DisasContext *ctx, unsigned reg)
{
if (likely(reg < 31)) {
return cpu_fir[reg];
} else {
return dest_sink(ctx);
}
}
static void gen_excp_1(int exception, int error_code)
{
TCGv_i32 tmp1, tmp2;
tmp1 = tcg_const_i32(exception);
tmp2 = tcg_const_i32(error_code);
gen_helper_excp(cpu_env, tmp1, tmp2);
tcg_temp_free_i32(tmp2);
tcg_temp_free_i32(tmp1);
}
static ExitStatus gen_excp(DisasContext *ctx, int exception, int error_code)
{
tcg_gen_movi_i64(cpu_pc, ctx->pc);
gen_excp_1(exception, error_code);
return EXIT_NORETURN;
}
static inline ExitStatus gen_invalid(DisasContext *ctx)
{
return gen_excp(ctx, EXCP_OPCDEC, 0);
}
static inline void gen_qemu_ldf(TCGv t0, TCGv t1, int flags)
{
TCGv_i32 tmp32 = tcg_temp_new_i32();
tcg_gen_qemu_ld_i32(tmp32, t1, flags, MO_LEUL);
gen_helper_memory_to_f(t0, tmp32);
tcg_temp_free_i32(tmp32);
}
static inline void gen_qemu_ldg(TCGv t0, TCGv t1, int flags)
{
TCGv tmp = tcg_temp_new();
tcg_gen_qemu_ld_i64(tmp, t1, flags, MO_LEQ);
gen_helper_memory_to_g(t0, tmp);
tcg_temp_free(tmp);
}
static inline void gen_qemu_lds(TCGv t0, TCGv t1, int flags)
{
TCGv_i32 tmp32 = tcg_temp_new_i32();
tcg_gen_qemu_ld_i32(tmp32, t1, flags, MO_LEUL);
gen_helper_memory_to_s(t0, tmp32);
tcg_temp_free_i32(tmp32);
}
static inline void gen_qemu_ldl_l(TCGv t0, TCGv t1, int flags)
{
tcg_gen_qemu_ld_i64(t0, t1, flags, MO_LESL);
tcg_gen_mov_i64(cpu_lock_addr, t1);
tcg_gen_mov_i64(cpu_lock_value, t0);
}
static inline void gen_qemu_ldq_l(TCGv t0, TCGv t1, int flags)
{
tcg_gen_qemu_ld_i64(t0, t1, flags, MO_LEQ);
tcg_gen_mov_i64(cpu_lock_addr, t1);
tcg_gen_mov_i64(cpu_lock_value, t0);
}
static inline void gen_load_mem(DisasContext *ctx,
void (*tcg_gen_qemu_load)(TCGv t0, TCGv t1,
int flags),
int ra, int rb, int32_t disp16, bool fp,
bool clear)
{
TCGv tmp, addr, va;
/* LDQ_U with ra $31 is UNOP. Other various loads are forms of
prefetches, which we can treat as nops. No worries about
missed exceptions here. */
if (unlikely(ra == 31)) {
return;
}
tmp = tcg_temp_new();
addr = load_gpr(ctx, rb);
if (disp16) {
tcg_gen_addi_i64(tmp, addr, disp16);
addr = tmp;
}
if (clear) {
tcg_gen_andi_i64(tmp, addr, ~0x7);
addr = tmp;
}
va = (fp ? cpu_fir[ra] : cpu_ir[ra]);
tcg_gen_qemu_load(va, addr, ctx->mem_idx);
tcg_temp_free(tmp);
}
static inline void gen_qemu_stf(TCGv t0, TCGv t1, int flags)
{
TCGv_i32 tmp32 = tcg_temp_new_i32();
gen_helper_f_to_memory(tmp32, t0);
tcg_gen_qemu_st_i32(tmp32, t1, flags, MO_LEUL);
tcg_temp_free_i32(tmp32);
}
static inline void gen_qemu_stg(TCGv t0, TCGv t1, int flags)
{
TCGv tmp = tcg_temp_new();
gen_helper_g_to_memory(tmp, t0);
tcg_gen_qemu_st_i64(tmp, t1, flags, MO_LEQ);
tcg_temp_free(tmp);
}
static inline void gen_qemu_sts(TCGv t0, TCGv t1, int flags)
{
TCGv_i32 tmp32 = tcg_temp_new_i32();
gen_helper_s_to_memory(tmp32, t0);
tcg_gen_qemu_st_i32(tmp32, t1, flags, MO_LEUL);
tcg_temp_free_i32(tmp32);
}
static inline void gen_store_mem(DisasContext *ctx,
void (*tcg_gen_qemu_store)(TCGv t0, TCGv t1,
int flags),
int ra, int rb, int32_t disp16, bool fp,
bool clear)
{
TCGv tmp, addr, va;
tmp = tcg_temp_new();
addr = load_gpr(ctx, rb);
if (disp16) {
tcg_gen_addi_i64(tmp, addr, disp16);
addr = tmp;
}
if (clear) {
tcg_gen_andi_i64(tmp, addr, ~0x7);
addr = tmp;
}
va = (fp ? load_fpr(ctx, ra) : load_gpr(ctx, ra));
tcg_gen_qemu_store(va, addr, ctx->mem_idx);
tcg_temp_free(tmp);
}
static ExitStatus gen_store_conditional(DisasContext *ctx, int ra, int rb,
int32_t disp16, int quad)
{
TCGv addr;
if (ra == 31) {
/* ??? Don't bother storing anything. The user can't tell
the difference, since the zero register always reads zero. */
return NO_EXIT;
}
#if defined(CONFIG_USER_ONLY)
addr = cpu_lock_st_addr;
#else
addr = tcg_temp_local_new();
#endif
tcg_gen_addi_i64(addr, load_gpr(ctx, rb), disp16);
#if defined(CONFIG_USER_ONLY)
/* ??? This is handled via a complicated version of compare-and-swap
in the cpu_loop. Hopefully one day we'll have a real CAS opcode
in TCG so that this isn't necessary. */
return gen_excp(ctx, quad ? EXCP_STQ_C : EXCP_STL_C, ra);
#else
/* ??? In system mode we are never multi-threaded, so CAS can be
implemented via a non-atomic load-compare-store sequence. */
{
TCGLabel *lab_fail, *lab_done;
TCGv val;
lab_fail = gen_new_label();
lab_done = gen_new_label();
tcg_gen_brcond_i64(TCG_COND_NE, addr, cpu_lock_addr, lab_fail);
val = tcg_temp_new();
tcg_gen_qemu_ld_i64(val, addr, ctx->mem_idx, quad ? MO_LEQ : MO_LESL);
tcg_gen_brcond_i64(TCG_COND_NE, val, cpu_lock_value, lab_fail);
tcg_gen_qemu_st_i64(cpu_ir[ra], addr, ctx->mem_idx,
quad ? MO_LEQ : MO_LEUL);
tcg_gen_movi_i64(cpu_ir[ra], 1);
tcg_gen_br(lab_done);
gen_set_label(lab_fail);
tcg_gen_movi_i64(cpu_ir[ra], 0);
gen_set_label(lab_done);
tcg_gen_movi_i64(cpu_lock_addr, -1);
tcg_temp_free(addr);
return NO_EXIT;
}
#endif
}
static bool in_superpage(DisasContext *ctx, int64_t addr)
{
return ((ctx->tb->flags & TB_FLAGS_USER_MODE) == 0
&& addr < 0
&& ((addr >> 41) & 3) == 2
&& addr >> TARGET_VIRT_ADDR_SPACE_BITS == addr >> 63);
}
static bool use_goto_tb(DisasContext *ctx, uint64_t dest)
{
/* Suppress goto_tb in the case of single-steping and IO. */
if ((ctx->tb->cflags & CF_LAST_IO)
|| ctx->singlestep_enabled || singlestep) {
return false;
}
/* If the destination is in the superpage, the page perms can't change. */
if (in_superpage(ctx, dest)) {
return true;
}
/* Check for the dest on the same page as the start of the TB. */
return ((ctx->tb->pc ^ dest) & TARGET_PAGE_MASK) == 0;
}
static ExitStatus gen_bdirect(DisasContext *ctx, int ra, int32_t disp)
{
uint64_t dest = ctx->pc + (disp << 2);
if (ra != 31) {
tcg_gen_movi_i64(cpu_ir[ra], ctx->pc);
}
/* Notice branch-to-next; used to initialize RA with the PC. */
if (disp == 0) {
return 0;
} else if (use_goto_tb(ctx, dest)) {
tcg_gen_goto_tb(0);
tcg_gen_movi_i64(cpu_pc, dest);
tcg_gen_exit_tb((uintptr_t)ctx->tb);
return EXIT_GOTO_TB;
} else {
tcg_gen_movi_i64(cpu_pc, dest);
return EXIT_PC_UPDATED;
}
}
static ExitStatus gen_bcond_internal(DisasContext *ctx, TCGCond cond,
TCGv cmp, int32_t disp)
{
uint64_t dest = ctx->pc + (disp << 2);
TCGLabel *lab_true = gen_new_label();
if (use_goto_tb(ctx, dest)) {
tcg_gen_brcondi_i64(cond, cmp, 0, lab_true);
tcg_gen_goto_tb(0);
tcg_gen_movi_i64(cpu_pc, ctx->pc);
tcg_gen_exit_tb((uintptr_t)ctx->tb);
gen_set_label(lab_true);
tcg_gen_goto_tb(1);
tcg_gen_movi_i64(cpu_pc, dest);
tcg_gen_exit_tb((uintptr_t)ctx->tb + 1);
return EXIT_GOTO_TB;
} else {
TCGv_i64 z = tcg_const_i64(0);
TCGv_i64 d = tcg_const_i64(dest);
TCGv_i64 p = tcg_const_i64(ctx->pc);
tcg_gen_movcond_i64(cond, cpu_pc, cmp, z, d, p);
tcg_temp_free_i64(z);
tcg_temp_free_i64(d);
tcg_temp_free_i64(p);
return EXIT_PC_UPDATED;
}
}
static ExitStatus gen_bcond(DisasContext *ctx, TCGCond cond, int ra,
int32_t disp, int mask)
{
TCGv cmp_tmp;
if (mask) {
cmp_tmp = tcg_temp_new();
tcg_gen_andi_i64(cmp_tmp, load_gpr(ctx, ra), 1);
} else {
cmp_tmp = load_gpr(ctx, ra);
}
return gen_bcond_internal(ctx, cond, cmp_tmp, disp);
}
/* Fold -0.0 for comparison with COND. */
static void gen_fold_mzero(TCGCond cond, TCGv dest, TCGv src)
{
uint64_t mzero = 1ull << 63;
switch (cond) {
case TCG_COND_LE:
case TCG_COND_GT:
/* For <= or >, the -0.0 value directly compares the way we want. */
tcg_gen_mov_i64(dest, src);
break;
case TCG_COND_EQ:
case TCG_COND_NE:
/* For == or !=, we can simply mask off the sign bit and compare. */
tcg_gen_andi_i64(dest, src, mzero - 1);
break;
case TCG_COND_GE:
case TCG_COND_LT:
/* For >= or <, map -0.0 to +0.0 via comparison and mask. */
tcg_gen_setcondi_i64(TCG_COND_NE, dest, src, mzero);
tcg_gen_neg_i64(dest, dest);
tcg_gen_and_i64(dest, dest, src);
break;
default:
abort();
}
}
static ExitStatus gen_fbcond(DisasContext *ctx, TCGCond cond, int ra,
int32_t disp)
{
TCGv cmp_tmp = tcg_temp_new();
gen_fold_mzero(cond, cmp_tmp, load_fpr(ctx, ra));
return gen_bcond_internal(ctx, cond, cmp_tmp, disp);
}
static void gen_fcmov(DisasContext *ctx, TCGCond cond, int ra, int rb, int rc)
{
TCGv_i64 va, vb, z;
z = load_zero(ctx);
vb = load_fpr(ctx, rb);
va = tcg_temp_new();
gen_fold_mzero(cond, va, load_fpr(ctx, ra));
tcg_gen_movcond_i64(cond, dest_fpr(ctx, rc), va, z, vb, load_fpr(ctx, rc));
tcg_temp_free(va);
}
#define QUAL_RM_N 0x080 /* Round mode nearest even */
#define QUAL_RM_C 0x000 /* Round mode chopped */
#define QUAL_RM_M 0x040 /* Round mode minus infinity */
#define QUAL_RM_D 0x0c0 /* Round mode dynamic */
#define QUAL_RM_MASK 0x0c0
#define QUAL_U 0x100 /* Underflow enable (fp output) */
#define QUAL_V 0x100 /* Overflow enable (int output) */
#define QUAL_S 0x400 /* Software completion enable */
#define QUAL_I 0x200 /* Inexact detection enable */
static void gen_qual_roundmode(DisasContext *ctx, int fn11)
{
TCGv_i32 tmp;
fn11 &= QUAL_RM_MASK;
if (fn11 == ctx->tb_rm) {
return;
}
ctx->tb_rm = fn11;
tmp = tcg_temp_new_i32();
switch (fn11) {
case QUAL_RM_N:
tcg_gen_movi_i32(tmp, float_round_nearest_even);
break;
case QUAL_RM_C:
tcg_gen_movi_i32(tmp, float_round_to_zero);
break;
case QUAL_RM_M:
tcg_gen_movi_i32(tmp, float_round_down);
break;
case QUAL_RM_D:
tcg_gen_ld8u_i32(tmp, cpu_env,
offsetof(CPUAlphaState, fpcr_dyn_round));
break;
}
#if defined(CONFIG_SOFTFLOAT_INLINE)
/* ??? The "fpu/softfloat.h" interface is to call set_float_rounding_mode.
With CONFIG_SOFTFLOAT that expands to an out-of-line call that just
sets the one field. */
tcg_gen_st8_i32(tmp, cpu_env,
offsetof(CPUAlphaState, fp_status.float_rounding_mode));
#else
gen_helper_setroundmode(tmp);
#endif
tcg_temp_free_i32(tmp);
}
static void gen_qual_flushzero(DisasContext *ctx, int fn11)
{
TCGv_i32 tmp;
fn11 &= QUAL_U;
if (fn11 == ctx->tb_ftz) {
return;
}
ctx->tb_ftz = fn11;
tmp = tcg_temp_new_i32();
if (fn11) {
/* Underflow is enabled, use the FPCR setting. */
tcg_gen_ld8u_i32(tmp, cpu_env,
offsetof(CPUAlphaState, fpcr_flush_to_zero));
} else {
/* Underflow is disabled, force flush-to-zero. */
tcg_gen_movi_i32(tmp, 1);