The A32/T32 gen_intermediate_code_internal() is complicated because it has to deal with: * conditionally executed instructions * Thumb IT blocks * kernel helper page * M profile exception-exit special casing None of these apply to A64, so putting the "this is A64 so call the A64 decoder" check in the middle of the A32/T32 loop is confusing and means the A64 decoder's handling of things like conditional jump and singlestepping has to take account of the conditional-execution jumps the main loop might emit. Refactor the code to give A64 its own gen_intermediate_code_internal function instead. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <rth@twiddle.net>
341 lines
9.5 KiB
C
341 lines
9.5 KiB
C
/*
|
|
* AArch64 translation
|
|
*
|
|
* Copyright (c) 2013 Alexander Graf <agraf@suse.de>
|
|
*
|
|
* 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 <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "cpu.h"
|
|
#include "tcg-op.h"
|
|
#include "qemu/log.h"
|
|
#include "translate.h"
|
|
#include "qemu/host-utils.h"
|
|
|
|
#include "exec/gen-icount.h"
|
|
|
|
#include "helper.h"
|
|
#define GEN_HELPER 1
|
|
#include "helper.h"
|
|
|
|
static TCGv_i64 cpu_X[32];
|
|
static TCGv_i64 cpu_pc;
|
|
static TCGv_i32 pstate;
|
|
|
|
static const char *regnames[] = {
|
|
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
|
|
"x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
|
|
"x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
|
|
"x24", "x25", "x26", "x27", "x28", "x29", "lr", "sp"
|
|
};
|
|
|
|
/* initialize TCG globals. */
|
|
void a64_translate_init(void)
|
|
{
|
|
int i;
|
|
|
|
cpu_pc = tcg_global_mem_new_i64(TCG_AREG0,
|
|
offsetof(CPUARMState, pc),
|
|
"pc");
|
|
for (i = 0; i < 32; i++) {
|
|
cpu_X[i] = tcg_global_mem_new_i64(TCG_AREG0,
|
|
offsetof(CPUARMState, xregs[i]),
|
|
regnames[i]);
|
|
}
|
|
|
|
pstate = tcg_global_mem_new_i32(TCG_AREG0,
|
|
offsetof(CPUARMState, pstate),
|
|
"pstate");
|
|
}
|
|
|
|
void aarch64_cpu_dump_state(CPUState *cs, FILE *f,
|
|
fprintf_function cpu_fprintf, int flags)
|
|
{
|
|
ARMCPU *cpu = ARM_CPU(cs);
|
|
CPUARMState *env = &cpu->env;
|
|
uint32_t psr = pstate_read(env);
|
|
int i;
|
|
|
|
cpu_fprintf(f, "PC=%016"PRIx64" SP=%016"PRIx64"\n",
|
|
env->pc, env->xregs[31]);
|
|
for (i = 0; i < 31; i++) {
|
|
cpu_fprintf(f, "X%02d=%016"PRIx64, i, env->xregs[i]);
|
|
if ((i % 4) == 3) {
|
|
cpu_fprintf(f, "\n");
|
|
} else {
|
|
cpu_fprintf(f, " ");
|
|
}
|
|
}
|
|
cpu_fprintf(f, "PSTATE=%08x (flags %c%c%c%c)\n",
|
|
psr,
|
|
psr & PSTATE_N ? 'N' : '-',
|
|
psr & PSTATE_Z ? 'Z' : '-',
|
|
psr & PSTATE_C ? 'C' : '-',
|
|
psr & PSTATE_V ? 'V' : '-');
|
|
cpu_fprintf(f, "\n");
|
|
}
|
|
|
|
void gen_a64_set_pc_im(uint64_t val)
|
|
{
|
|
tcg_gen_movi_i64(cpu_pc, val);
|
|
}
|
|
|
|
static void gen_exception(int excp)
|
|
{
|
|
TCGv_i32 tmp = tcg_temp_new_i32();
|
|
tcg_gen_movi_i32(tmp, excp);
|
|
gen_helper_exception(cpu_env, tmp);
|
|
tcg_temp_free_i32(tmp);
|
|
}
|
|
|
|
static void gen_exception_insn(DisasContext *s, int offset, int excp)
|
|
{
|
|
gen_a64_set_pc_im(s->pc - offset);
|
|
gen_exception(excp);
|
|
s->is_jmp = DISAS_EXC;
|
|
}
|
|
|
|
static inline bool use_goto_tb(DisasContext *s, int n, uint64_t dest)
|
|
{
|
|
/* No direct tb linking with singlestep or deterministic io */
|
|
if (s->singlestep_enabled || (s->tb->cflags & CF_LAST_IO)) {
|
|
return false;
|
|
}
|
|
|
|
/* Only link tbs from inside the same guest page */
|
|
if ((s->tb->pc & TARGET_PAGE_MASK) != (dest & TARGET_PAGE_MASK)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void gen_goto_tb(DisasContext *s, int n, uint64_t dest)
|
|
{
|
|
TranslationBlock *tb;
|
|
|
|
tb = s->tb;
|
|
if (use_goto_tb(s, n, dest)) {
|
|
tcg_gen_goto_tb(n);
|
|
gen_a64_set_pc_im(dest);
|
|
tcg_gen_exit_tb((tcg_target_long)tb + n);
|
|
s->is_jmp = DISAS_TB_JUMP;
|
|
} else {
|
|
gen_a64_set_pc_im(dest);
|
|
if (s->singlestep_enabled) {
|
|
gen_exception(EXCP_DEBUG);
|
|
}
|
|
tcg_gen_exit_tb(0);
|
|
s->is_jmp = DISAS_JUMP;
|
|
}
|
|
}
|
|
|
|
static void real_unallocated_encoding(DisasContext *s)
|
|
{
|
|
fprintf(stderr, "Unknown instruction: %#x\n", s->insn);
|
|
gen_exception_insn(s, 4, EXCP_UDEF);
|
|
}
|
|
|
|
#define unallocated_encoding(s) do { \
|
|
fprintf(stderr, "unallocated encoding at line: %d\n", __LINE__); \
|
|
real_unallocated_encoding(s); \
|
|
} while (0)
|
|
|
|
static void disas_a64_insn(CPUARMState *env, DisasContext *s)
|
|
{
|
|
uint32_t insn;
|
|
|
|
insn = arm_ldl_code(env, s->pc, s->bswap_code);
|
|
s->insn = insn;
|
|
s->pc += 4;
|
|
|
|
switch ((insn >> 24) & 0x1f) {
|
|
default:
|
|
unallocated_encoding(s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gen_intermediate_code_internal_a64(ARMCPU *cpu,
|
|
TranslationBlock *tb,
|
|
bool search_pc)
|
|
{
|
|
CPUState *cs = CPU(cpu);
|
|
CPUARMState *env = &cpu->env;
|
|
DisasContext dc1, *dc = &dc1;
|
|
CPUBreakpoint *bp;
|
|
uint16_t *gen_opc_end;
|
|
int j, lj;
|
|
target_ulong pc_start;
|
|
target_ulong next_page_start;
|
|
int num_insns;
|
|
int max_insns;
|
|
|
|
pc_start = tb->pc;
|
|
|
|
dc->tb = tb;
|
|
|
|
gen_opc_end = tcg_ctx.gen_opc_buf + OPC_MAX_SIZE;
|
|
|
|
dc->is_jmp = DISAS_NEXT;
|
|
dc->pc = pc_start;
|
|
dc->singlestep_enabled = cs->singlestep_enabled;
|
|
dc->condjmp = 0;
|
|
|
|
dc->aarch64 = 1;
|
|
dc->thumb = 0;
|
|
dc->bswap_code = 0;
|
|
dc->condexec_mask = 0;
|
|
dc->condexec_cond = 0;
|
|
#if !defined(CONFIG_USER_ONLY)
|
|
dc->user = 0;
|
|
#endif
|
|
dc->vfp_enabled = 0;
|
|
dc->vec_len = 0;
|
|
dc->vec_stride = 0;
|
|
|
|
next_page_start = (pc_start & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE;
|
|
lj = -1;
|
|
num_insns = 0;
|
|
max_insns = tb->cflags & CF_COUNT_MASK;
|
|
if (max_insns == 0) {
|
|
max_insns = CF_COUNT_MASK;
|
|
}
|
|
|
|
gen_tb_start();
|
|
|
|
tcg_clear_temp_count();
|
|
|
|
do {
|
|
if (unlikely(!QTAILQ_EMPTY(&env->breakpoints))) {
|
|
QTAILQ_FOREACH(bp, &env->breakpoints, entry) {
|
|
if (bp->pc == dc->pc) {
|
|
gen_exception_insn(dc, 0, EXCP_DEBUG);
|
|
/* Advance PC so that clearing the breakpoint will
|
|
invalidate this TB. */
|
|
dc->pc += 2;
|
|
goto done_generating;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (search_pc) {
|
|
j = tcg_ctx.gen_opc_ptr - tcg_ctx.gen_opc_buf;
|
|
if (lj < j) {
|
|
lj++;
|
|
while (lj < j) {
|
|
tcg_ctx.gen_opc_instr_start[lj++] = 0;
|
|
}
|
|
}
|
|
tcg_ctx.gen_opc_pc[lj] = dc->pc;
|
|
tcg_ctx.gen_opc_instr_start[lj] = 1;
|
|
tcg_ctx.gen_opc_icount[lj] = num_insns;
|
|
}
|
|
|
|
if (num_insns + 1 == max_insns && (tb->cflags & CF_LAST_IO)) {
|
|
gen_io_start();
|
|
}
|
|
|
|
if (unlikely(qemu_loglevel_mask(CPU_LOG_TB_OP | CPU_LOG_TB_OP_OPT))) {
|
|
tcg_gen_debug_insn_start(dc->pc);
|
|
}
|
|
|
|
disas_a64_insn(env, dc);
|
|
|
|
if (tcg_check_temp_count()) {
|
|
fprintf(stderr, "TCG temporary leak before "TARGET_FMT_lx"\n",
|
|
dc->pc);
|
|
}
|
|
|
|
/* Translation stops when a conditional branch is encountered.
|
|
* Otherwise the subsequent code could get translated several times.
|
|
* Also stop translation when a page boundary is reached. This
|
|
* ensures prefetch aborts occur at the right place.
|
|
*/
|
|
num_insns++;
|
|
} while (!dc->is_jmp && tcg_ctx.gen_opc_ptr < gen_opc_end &&
|
|
!cs->singlestep_enabled &&
|
|
!singlestep &&
|
|
dc->pc < next_page_start &&
|
|
num_insns < max_insns);
|
|
|
|
if (tb->cflags & CF_LAST_IO) {
|
|
gen_io_end();
|
|
}
|
|
|
|
if (unlikely(cs->singlestep_enabled) && dc->is_jmp != DISAS_EXC) {
|
|
/* Note that this means single stepping WFI doesn't halt the CPU.
|
|
* For conditional branch insns this is harmless unreachable code as
|
|
* gen_goto_tb() has already handled emitting the debug exception
|
|
* (and thus a tb-jump is not possible when singlestepping).
|
|
*/
|
|
assert(dc->is_jmp != DISAS_TB_JUMP);
|
|
if (dc->is_jmp != DISAS_JUMP) {
|
|
gen_a64_set_pc_im(dc->pc);
|
|
}
|
|
gen_exception(EXCP_DEBUG);
|
|
} else {
|
|
switch (dc->is_jmp) {
|
|
case DISAS_NEXT:
|
|
gen_goto_tb(dc, 1, dc->pc);
|
|
break;
|
|
default:
|
|
case DISAS_JUMP:
|
|
case DISAS_UPDATE:
|
|
/* indicate that the hash table must be used to find the next TB */
|
|
tcg_gen_exit_tb(0);
|
|
break;
|
|
case DISAS_TB_JUMP:
|
|
case DISAS_EXC:
|
|
case DISAS_SWI:
|
|
break;
|
|
case DISAS_WFI:
|
|
/* This is a special case because we don't want to just halt the CPU
|
|
* if trying to debug across a WFI.
|
|
*/
|
|
gen_helper_wfi(cpu_env);
|
|
break;
|
|
}
|
|
}
|
|
|
|
done_generating:
|
|
gen_tb_end(tb, num_insns);
|
|
*tcg_ctx.gen_opc_ptr = INDEX_op_end;
|
|
|
|
#ifdef DEBUG_DISAS
|
|
if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM)) {
|
|
qemu_log("----------------\n");
|
|
qemu_log("IN: %s\n", lookup_symbol(pc_start));
|
|
log_target_disas(env, pc_start, dc->pc - pc_start,
|
|
dc->thumb | (dc->bswap_code << 1));
|
|
qemu_log("\n");
|
|
}
|
|
#endif
|
|
if (search_pc) {
|
|
j = tcg_ctx.gen_opc_ptr - tcg_ctx.gen_opc_buf;
|
|
lj++;
|
|
while (lj <= j) {
|
|
tcg_ctx.gen_opc_instr_start[lj++] = 0;
|
|
}
|
|
} else {
|
|
tb->size = dc->pc - pc_start;
|
|
tb->icount = num_insns;
|
|
}
|
|
}
|