Message ID | 1626861198-6133-7-git-send-email-gaosong@loongson.cn (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add LoongArch linux-user emulation support | expand |
On 7/20/21 11:53 PM, Song Gao wrote: > +/* General purpose registers moves. */ > +void gen_load_gpr(TCGv t, int reg) > +{ > + if (reg == 0) { > + tcg_gen_movi_tl(t, 0); > + } else { > + tcg_gen_mov_tl(t, cpu_gpr[reg]); > + } > +} Please have a look at https://patchew.org/QEMU/20210709042608.883256-1-richard.henderson@linaro.org/ for a better way to handle the zero register. > +static inline void save_cpu_state(DisasContext *ctx, int do_save_pc) > +{ > + if (do_save_pc && ctx->base.pc_next != ctx->saved_pc) { > + gen_save_pc(ctx->base.pc_next); > + ctx->saved_pc = ctx->base.pc_next; > + } > + if (ctx->hflags != ctx->saved_hflags) { > + tcg_gen_movi_i32(hflags, ctx->hflags); > + ctx->saved_hflags = ctx->hflags; > + switch (ctx->hflags & LOONGARCH_HFLAG_BMASK) { > + case LOONGARCH_HFLAG_BR: > + break; > + case LOONGARCH_HFLAG_BC: > + case LOONGARCH_HFLAG_B: > + tcg_gen_movi_tl(btarget, ctx->btarget); > + break; > + } > + } > +} Drop all the hflags handling. It's all copied from mips delay slot handling. > + > +static inline void restore_cpu_state(CPULoongArchState *env, DisasContext *ctx) > +{ > + ctx->saved_hflags = ctx->hflags; > + switch (ctx->hflags & LOONGARCH_HFLAG_BMASK) { > + case LOONGARCH_HFLAG_BR: > + break; > + case LOONGARCH_HFLAG_BC: > + case LOONGARCH_HFLAG_B: > + ctx->btarget = env->btarget; > + break; > + } > +} Likewise. > +static void gen_load_fpr32h(TCGv_i32 t, int reg) > +{ > + tcg_gen_extrh_i64_i32(t, fpu_f64[reg]); > +} > + > +static void gen_store_fpr32h(TCGv_i32 t, int reg) > +{ > + TCGv_i64 t64 = tcg_temp_new_i64(); > + tcg_gen_extu_i32_i64(t64, t); > + tcg_gen_deposit_i64(fpu_f64[reg], fpu_f64[reg], t64, 32, 32); > + tcg_temp_free_i64(t64); > +} There is no general-purpose high-part fpr stuff. There's only movgr2frh and movfrh2gr, and you can simplify both if you drop the transition through TCGv_i32. > +void gen_op_addr_add(TCGv ret, TCGv arg0, TCGv arg1) > +{ > + tcg_gen_add_tl(ret, arg0, arg1); > +} No point in this, since loongarch has no 32-bit address mode. > +void gen_base_offset_addr(TCGv addr, int base, int offset) > +{ > + if (base == 0) { > + tcg_gen_movi_tl(addr, offset); > + } else if (offset == 0) { > + gen_load_gpr(addr, base); > + } else { > + tcg_gen_movi_tl(addr, offset); > + gen_op_addr_add(addr, cpu_gpr[base], addr); > + } > +} Using the interfaces I quote above from my riscv cleanup, this can be tidied to tcg_gen_addi_tl(addr, gpr_src(base), offset); > +static inline bool use_goto_tb(DisasContext *ctx, target_ulong dest) > +{ > + return true; > +} You must now use translate_use_goto_tb, which will not always return true. You will see assertion failures otherwise. > +static inline void clear_branch_hflags(DisasContext *ctx) > +{ > + ctx->hflags &= ~LOONGARCH_HFLAG_BMASK; > + if (ctx->base.is_jmp == DISAS_NEXT) { > + save_cpu_state(ctx, 0); > + } else { > + /* > + * It is not safe to save ctx->hflags as hflags may be changed > + * in execution time. > + */ > + tcg_gen_andi_i32(hflags, hflags, ~LOONGARCH_HFLAG_BMASK); > + } > +} Not required. > +static void gen_branch(DisasContext *ctx, int insn_bytes) > +{ > + if (ctx->hflags & LOONGARCH_HFLAG_BMASK) { > + int proc_hflags = ctx->hflags & LOONGARCH_HFLAG_BMASK; > + /* Branches completion */ > + clear_branch_hflags(ctx); > + ctx->base.is_jmp = DISAS_NORETURN; > + switch (proc_hflags & LOONGARCH_HFLAG_BMASK) { > + case LOONGARCH_HFLAG_B: > + /* unconditional branch */ > + gen_goto_tb(ctx, 0, ctx->btarget); > + break; > + case LOONGARCH_HFLAG_BC: > + /* Conditional branch */ > + { > + TCGLabel *l1 = gen_new_label(); > + > + tcg_gen_brcondi_tl(TCG_COND_NE, bcond, 0, l1); > + gen_goto_tb(ctx, 1, ctx->base.pc_next + insn_bytes); > + gen_set_label(l1); > + gen_goto_tb(ctx, 0, ctx->btarget); > + } > + break; > + case LOONGARCH_HFLAG_BR: > + /* unconditional branch to register */ > + tcg_gen_mov_tl(cpu_PC, btarget); > + tcg_gen_lookup_and_goto_ptr(); > + break; > + default: > + fprintf(stderr, "unknown branch 0x%x\n", proc_hflags); > + abort(); > + } > + } > +} Split this up into the various trans_* branch routines, without the setting of HFLAG. > +static void loongarch_tr_init_disas_context(DisasContextBase *dcbase, > + CPUState *cs) > +{ > + DisasContext *ctx = container_of(dcbase, DisasContext, base); > + CPULoongArchState *env = cs->env_ptr; > + > + ctx->page_start = ctx->base.pc_first & TARGET_PAGE_MASK; > + ctx->saved_pc = -1; > + ctx->btarget = 0; > + /* Restore state from the tb context. */ > + ctx->hflags = (uint32_t)ctx->base.tb->flags; > + restore_cpu_state(env, ctx); > + ctx->mem_idx = LOONGARCH_HFLAG_UM; This is not an mmu index. You didn't notice the error because you're only doing user-mode. You're missing a check for page crossing. Generally, for fixed-width ISAs like this, we do /* Bound the number of insns to execute to those left on the page. */ int bound = -(ctx->base.pc_first | TARGET_PAGE_MASK) / 4; ctx->base.max_insns = MIN(ctx->base.max_insns, bound); here in init_disas_context. > +static void loongarch_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) > +{ > + DisasContext *ctx = container_of(dcbase, DisasContext, base); > + > + tcg_gen_insn_start(ctx->base.pc_next, ctx->hflags & LOONGARCH_HFLAG_BMASK, > + ctx->btarget); No hflags/btarget stuff. Drop TARGET_INSN_START_EXTRA_WORDS. > +static bool loongarch_tr_breakpoint_check(DisasContextBase *dcbase, > + CPUState *cs, > + const CPUBreakpoint *bp) > +{ > + return true; > +} Broken, but now handled generically, so remove it. > +static void loongarch_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) > +{ > + CPULoongArchState *env = cs->env_ptr; > + DisasContext *ctx = container_of(dcbase, DisasContext, base); > + int insn_bytes = 4; > + > + ctx->opcode = cpu_ldl_code(env, ctx->base.pc_next); > + > + if (!decode(ctx, ctx->opcode)) { > + fprintf(stderr, "Error: unkown opcode. 0x%lx: 0x%x\n", > + ctx->base.pc_next, ctx->opcode); No fprintfs. Use qemu_log_mask with LOG_UNIMP or LOG_GUEST_ERROR. > + if (ctx->hflags & LOONGARCH_HFLAG_BMASK) { > + gen_branch(ctx, insn_bytes); > + } Drop this, as I mentioned above. > +static void fpu_dump_state(CPULoongArchState *env, FILE * f, int flags) > +{ > + int i; > + int is_fpu64 = 1; > + > +#define printfpr(fp) \ > + do { \ > + if (is_fpu64) \ > + qemu_fprintf(f, "w:%08x d:%016" PRIx64 \ > + " fd:%13g fs:%13g psu: %13g\n", \ > + (fp)->w[FP_ENDIAN_IDX], (fp)->d, \ > + (double)(fp)->fd, \ > + (double)(fp)->fs[FP_ENDIAN_IDX], \ > + (double)(fp)->fs[!FP_ENDIAN_IDX]); \ > + else { \ > + fpr_t tmp; \ > + tmp.w[FP_ENDIAN_IDX] = (fp)->w[FP_ENDIAN_IDX]; \ > + tmp.w[!FP_ENDIAN_IDX] = ((fp) + 1)->w[FP_ENDIAN_IDX]; \ > + qemu_fprintf(f, "w:%08x d:%016" PRIx64 \ > + " fd:%13g fs:%13g psu:%13g\n", \ > + tmp.w[FP_ENDIAN_IDX], tmp.d, \ > + (double)tmp.fd, \ > + (double)tmp.fs[FP_ENDIAN_IDX], \ > + (double)tmp.fs[!FP_ENDIAN_IDX]); \ > + } \ > + } while (0) This is broken. You're performing an integer to fp conversion of something that is already a floating-point value, not printing the floating-point value itself. It's broken in the mips code as well. In addition, is_fpu64 is pointless for loongarch. > +void loongarch_tcg_init(void) > +{ > + int i; > + > + for (i = 0; i < 32; i++) > + cpu_gpr[i] = tcg_global_mem_new(cpu_env, > + offsetof(CPULoongArchState, > + active_tc.gpr[i]), > + regnames[i]); Missing braces. Do not create a temp for the zero register. > + bcond = tcg_global_mem_new(cpu_env, > + offsetof(CPULoongArchState, bcond), "bcond"); > + btarget = tcg_global_mem_new(cpu_env, > + offsetof(CPULoongArchState, btarget), > + "btarget"); > + hflags = tcg_global_mem_new_i32(cpu_env, > + offsetof(CPULoongArchState, hflags), > + "hflags"); Drop these. r~
Hi, Richard. On 07/23/2021 07:50 AM, Richard Henderson wrote: > On 7/20/21 11:53 PM, Song Gao wrote: >> +/* General purpose registers moves. */ >> +void gen_load_gpr(TCGv t, int reg) >> +{ >> + if (reg == 0) { >> + tcg_gen_movi_tl(t, 0); >> + } else { >> + tcg_gen_mov_tl(t, cpu_gpr[reg]); >> + } >> +} > > Please have a look at > > https://patchew.org/QEMU/20210709042608.883256-1-richard.henderson@linaro.org/ > > for a better way to handle the zero register. > > OK, I'll look at it carefully. >> +static inline void save_cpu_state(DisasContext *ctx, int do_save_pc) >> +{ >> + if (do_save_pc && ctx->base.pc_next != ctx->saved_pc) { >> + gen_save_pc(ctx->base.pc_next); >> + ctx->saved_pc = ctx->base.pc_next; >> + } >> + if (ctx->hflags != ctx->saved_hflags) { >> + tcg_gen_movi_i32(hflags, ctx->hflags); >> + ctx->saved_hflags = ctx->hflags; >> + switch (ctx->hflags & LOONGARCH_HFLAG_BMASK) { >> + case LOONGARCH_HFLAG_BR: >> + break; >> + case LOONGARCH_HFLAG_BC: >> + case LOONGARCH_HFLAG_B: >> + tcg_gen_movi_tl(btarget, ctx->btarget); >> + break; >> + } >> + } >> +} > > Drop all the hflags handling. > It's all copied from mips delay slot handling. > OK. >> + >> +static inline void restore_cpu_state(CPULoongArchState *env, DisasContext *ctx) >> +{ >> + ctx->saved_hflags = ctx->hflags; >> + switch (ctx->hflags & LOONGARCH_HFLAG_BMASK) { >> + case LOONGARCH_HFLAG_BR: >> + break; >> + case LOONGARCH_HFLAG_BC: >> + case LOONGARCH_HFLAG_B: >> + ctx->btarget = env->btarget; >> + break; >> + } >> +} > > Likewise. > >> +static void gen_load_fpr32h(TCGv_i32 t, int reg) >> +{ >> + tcg_gen_extrh_i64_i32(t, fpu_f64[reg]); >> +} >> + >> +static void gen_store_fpr32h(TCGv_i32 t, int reg) >> +{ >> + TCGv_i64 t64 = tcg_temp_new_i64(); >> + tcg_gen_extu_i32_i64(t64, t); >> + tcg_gen_deposit_i64(fpu_f64[reg], fpu_f64[reg], t64, 32, 32); >> + tcg_temp_free_i64(t64); >> +} > > There is no general-purpose high-part fpr stuff. There's only movgr2frh and movfrh2gr, and you can simplify both if you drop the transition through TCGv_i32. > OK. >> +void gen_op_addr_add(TCGv ret, TCGv arg0, TCGv arg1) >> +{ >> + tcg_gen_add_tl(ret, arg0, arg1); >> +} > > No point in this, since loongarch has no 32-bit address mode. > OK. >> +void gen_base_offset_addr(TCGv addr, int base, int offset) >> +{ >> + if (base == 0) { >> + tcg_gen_movi_tl(addr, offset); >> + } else if (offset == 0) { >> + gen_load_gpr(addr, base); >> + } else { >> + tcg_gen_movi_tl(addr, offset); >> + gen_op_addr_add(addr, cpu_gpr[base], addr); >> + } >> +} > > Using the interfaces I quote above from my riscv cleanup, > this can be tidied to > > tcg_gen_addi_tl(addr, gpr_src(base), offset); > 'riscv cleanup' series at https://patchew.org/QEMU/20210709042608.883256-1-richard.henderson@linaro.org/ , Right? >> +static inline bool use_goto_tb(DisasContext *ctx, target_ulong dest) >> +{ >> + return true; >> +} > > You must now use translate_use_goto_tb, which will not always return true. You will see assertion failures otherwise. > I see the patch already. >> +static inline void clear_branch_hflags(DisasContext *ctx) >> +{ >> + ctx->hflags &= ~LOONGARCH_HFLAG_BMASK; >> + if (ctx->base.is_jmp == DISAS_NEXT) { >> + save_cpu_state(ctx, 0); >> + } else { >> + /* >> + * It is not safe to save ctx->hflags as hflags may be changed >> + * in execution time. >> + */ >> + tcg_gen_andi_i32(hflags, hflags, ~LOONGARCH_HFLAG_BMASK); >> + } >> +} > > Not required. > >> +static void gen_branch(DisasContext *ctx, int insn_bytes) >> +{ >> + if (ctx->hflags & LOONGARCH_HFLAG_BMASK) { >> + int proc_hflags = ctx->hflags & LOONGARCH_HFLAG_BMASK; >> + /* Branches completion */ >> + clear_branch_hflags(ctx); >> + ctx->base.is_jmp = DISAS_NORETURN; >> + switch (proc_hflags & LOONGARCH_HFLAG_BMASK) { >> + case LOONGARCH_HFLAG_B: >> + /* unconditional branch */ >> + gen_goto_tb(ctx, 0, ctx->btarget); >> + break; >> + case LOONGARCH_HFLAG_BC: >> + /* Conditional branch */ >> + { >> + TCGLabel *l1 = gen_new_label(); >> + >> + tcg_gen_brcondi_tl(TCG_COND_NE, bcond, 0, l1); >> + gen_goto_tb(ctx, 1, ctx->base.pc_next + insn_bytes); >> + gen_set_label(l1); >> + gen_goto_tb(ctx, 0, ctx->btarget); >> + } >> + break; >> + case LOONGARCH_HFLAG_BR: >> + /* unconditional branch to register */ >> + tcg_gen_mov_tl(cpu_PC, btarget); >> + tcg_gen_lookup_and_goto_ptr(); >> + break; >> + default: >> + fprintf(stderr, "unknown branch 0x%x\n", proc_hflags); >> + abort(); >> + } >> + } >> +} > > Split this up into the various trans_* branch routines, without the setting of HFLAG. > >> +static void loongarch_tr_init_disas_context(DisasContextBase *dcbase, >> + CPUState *cs) >> +{ >> + DisasContext *ctx = container_of(dcbase, DisasContext, base); >> + CPULoongArchState *env = cs->env_ptr; >> + >> + ctx->page_start = ctx->base.pc_first & TARGET_PAGE_MASK; >> + ctx->saved_pc = -1; >> + ctx->btarget = 0; >> + /* Restore state from the tb context. */ >> + ctx->hflags = (uint32_t)ctx->base.tb->flags; >> + restore_cpu_state(env, ctx); >> + ctx->mem_idx = LOONGARCH_HFLAG_UM; > > This is not an mmu index. You didn't notice the error because you're only doing user-mode. > > You're missing a check for page crossing. > Generally, for fixed-width ISAs like this, we do > > /* Bound the number of insns to execute to those left on the page. */ > int bound = -(ctx->base.pc_first | TARGET_PAGE_MASK) / 4; > ctx->base.max_insns = MIN(ctx->base.max_insns, bound); > > here in init_disas_context. > >> +static void loongarch_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) >> +{ >> + DisasContext *ctx = container_of(dcbase, DisasContext, base); >> + >> + tcg_gen_insn_start(ctx->base.pc_next, ctx->hflags & LOONGARCH_HFLAG_BMASK, >> + ctx->btarget); > > No hflags/btarget stuff. Drop TARGET_INSN_START_EXTRA_WORDS. > >> +static bool loongarch_tr_breakpoint_check(DisasContextBase *dcbase, >> + CPUState *cs, >> + const CPUBreakpoint *bp) >> +{ >> + return true; >> +} > > Broken, but now handled generically, so remove it. > > OK. >> +static void loongarch_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) >> +{ >> + CPULoongArchState *env = cs->env_ptr; >> + DisasContext *ctx = container_of(dcbase, DisasContext, base); >> + int insn_bytes = 4; >> + >> + ctx->opcode = cpu_ldl_code(env, ctx->base.pc_next); >> + >> + if (!decode(ctx, ctx->opcode)) { >> + fprintf(stderr, "Error: unkown opcode. 0x%lx: 0x%x\n", >> + ctx->base.pc_next, ctx->opcode); > > No fprintfs. Use qemu_log_mask with LOG_UNIMP or LOG_GUEST_ERROR. > OK. >> + if (ctx->hflags & LOONGARCH_HFLAG_BMASK) { >> + gen_branch(ctx, insn_bytes); >> + } > > Drop this, as I mentioned above. > OK. >> +static void fpu_dump_state(CPULoongArchState *env, FILE * f, int flags) >> +{ >> + int i; >> + int is_fpu64 = 1; >> + >> +#define printfpr(fp) \ >> + do { \ >> + if (is_fpu64) \ >> + qemu_fprintf(f, "w:%08x d:%016" PRIx64 \ >> + " fd:%13g fs:%13g psu: %13g\n", \ >> + (fp)->w[FP_ENDIAN_IDX], (fp)->d, \ >> + (double)(fp)->fd, \ >> + (double)(fp)->fs[FP_ENDIAN_IDX], \ >> + (double)(fp)->fs[!FP_ENDIAN_IDX]); \ >> + else { \ >> + fpr_t tmp; \ >> + tmp.w[FP_ENDIAN_IDX] = (fp)->w[FP_ENDIAN_IDX]; \ >> + tmp.w[!FP_ENDIAN_IDX] = ((fp) + 1)->w[FP_ENDIAN_IDX]; \ >> + qemu_fprintf(f, "w:%08x d:%016" PRIx64 \ >> + " fd:%13g fs:%13g psu:%13g\n", \ >> + tmp.w[FP_ENDIAN_IDX], tmp.d, \ >> + (double)tmp.fd, \ >> + (double)tmp.fs[FP_ENDIAN_IDX], \ >> + (double)tmp.fs[!FP_ENDIAN_IDX]); \ >> + } \ >> + } while (0) > > This is broken. You're performing an integer to fp conversion of something that is already a floating-point value, not printing the floating-point value itself. It's broken in the mips code as well. > > In addition, is_fpu64 is pointless for loongarch. > Yes. >> +void loongarch_tcg_init(void) >> +{ >> + int i; >> + >> + for (i = 0; i < 32; i++) >> + cpu_gpr[i] = tcg_global_mem_new(cpu_env, >> + offsetof(CPULoongArchState, >> + active_tc.gpr[i]), >> + regnames[i]); > > Missing braces. > Do not create a temp for the zero register. > >> + bcond = tcg_global_mem_new(cpu_env, >> + offsetof(CPULoongArchState, bcond), "bcond"); >> + btarget = tcg_global_mem_new(cpu_env, >> + offsetof(CPULoongArchState, btarget), >> + "btarget"); >> + hflags = tcg_global_mem_new_i32(cpu_env, >> + offsetof(CPULoongArchState, hflags), >> + "hflags"); > > Drop these. OK. Thanks for you kindly help. Thanks Song Gao.
On 7/25/21 11:39 PM, Song Gao wrote: >>> +void gen_base_offset_addr(TCGv addr, int base, int offset) >>> +{ >>> + if (base == 0) { >>> + tcg_gen_movi_tl(addr, offset); >>> + } else if (offset == 0) { >>> + gen_load_gpr(addr, base); >>> + } else { >>> + tcg_gen_movi_tl(addr, offset); >>> + gen_op_addr_add(addr, cpu_gpr[base], addr); >>> + } >>> +} >> >> Using the interfaces I quote above from my riscv cleanup, >> this can be tidied to >> >> tcg_gen_addi_tl(addr, gpr_src(base), offset); >> > > 'riscv cleanup' series at https://patchew.org/QEMU/20210709042608.883256-1-richard.henderson@linaro.org/ , Right? Yes. r~
diff --git a/target/loongarch/helper.h b/target/loongarch/helper.h new file mode 100644 index 0000000..6c7e19b --- /dev/null +++ b/target/loongarch/helper.h @@ -0,0 +1,10 @@ +/* + * QEMU LoongArch CPU + * + * Copyright (c) 2021 Loongson Technology Corporation Limited + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +DEF_HELPER_3(raise_exception_err, noreturn, env, i32, int) +DEF_HELPER_2(raise_exception, noreturn, env, i32) diff --git a/target/loongarch/op_helper.c b/target/loongarch/op_helper.c new file mode 100644 index 0000000..b2cbdd7 --- /dev/null +++ b/target/loongarch/op_helper.c @@ -0,0 +1,27 @@ +/* + * LoongArch emulation helpers for qemu. + * + * Copyright (c) 2021 Loongson Technology Corporation Limited + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "qemu/osdep.h" +#include "qemu/main-loop.h" +#include "cpu.h" +#include "qemu/host-utils.h" +#include "exec/helper-proto.h" +#include "exec/exec-all.h" +#include "exec/cpu_ldst.h" + +/* Exceptions helpers */ +void helper_raise_exception_err(CPULoongArchState *env, uint32_t exception, + int error_code) +{ + do_raise_exception_err(env, exception, error_code, 0); +} + +void helper_raise_exception(CPULoongArchState *env, uint32_t exception) +{ + do_raise_exception(env, exception, GETPC()); +} diff --git a/target/loongarch/translate.c b/target/loongarch/translate.c new file mode 100644 index 0000000..531f7e1 --- /dev/null +++ b/target/loongarch/translate.c @@ -0,0 +1,485 @@ +/* + * LoongArch emulation for QEMU - main translation routines. + * + * Copyright (c) 2021 Loongson Technology Corporation Limited + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "tcg/tcg-op.h" +#include "exec/translator.h" +#include "exec/helper-proto.h" +#include "exec/helper-gen.h" +#include "semihosting/semihost.h" + +#include "exec/translator.h" +#include "exec/log.h" +#include "qemu/qemu-print.h" +#include "fpu_helper.h" +#include "translate.h" + +/* global register indices */ +TCGv cpu_gpr[32], cpu_PC; +TCGv btarget, bcond; +static TCGv cpu_lladdr, cpu_llval; +static TCGv_i32 hflags; +TCGv_i32 fpu_fcsr0; +TCGv_i64 fpu_f64[32]; + +#include "exec/gen-icount.h" + +#define DISAS_STOP DISAS_TARGET_0 +#define DISAS_EXIT DISAS_TARGET_1 + +static const char * const regnames[] = { + "r0", "ra", "tp", "sp", "a0", "a1", "a2", "a3", + "a4", "a5", "a6", "a7", "t0", "t1", "t2", "t3", + "t4", "t5", "t6", "t7", "t8", "x0", "fp", "s0", + "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", +}; + +static const char * const fregnames[] = { + "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", "f31", +}; + +/* General purpose registers moves. */ +void gen_load_gpr(TCGv t, int reg) +{ + if (reg == 0) { + tcg_gen_movi_tl(t, 0); + } else { + tcg_gen_mov_tl(t, cpu_gpr[reg]); + } +} + +static inline void gen_save_pc(target_ulong pc) +{ + tcg_gen_movi_tl(cpu_PC, pc); +} + +static inline void save_cpu_state(DisasContext *ctx, int do_save_pc) +{ + if (do_save_pc && ctx->base.pc_next != ctx->saved_pc) { + gen_save_pc(ctx->base.pc_next); + ctx->saved_pc = ctx->base.pc_next; + } + if (ctx->hflags != ctx->saved_hflags) { + tcg_gen_movi_i32(hflags, ctx->hflags); + ctx->saved_hflags = ctx->hflags; + switch (ctx->hflags & LOONGARCH_HFLAG_BMASK) { + case LOONGARCH_HFLAG_BR: + break; + case LOONGARCH_HFLAG_BC: + case LOONGARCH_HFLAG_B: + tcg_gen_movi_tl(btarget, ctx->btarget); + break; + } + } +} + +static inline void restore_cpu_state(CPULoongArchState *env, DisasContext *ctx) +{ + ctx->saved_hflags = ctx->hflags; + switch (ctx->hflags & LOONGARCH_HFLAG_BMASK) { + case LOONGARCH_HFLAG_BR: + break; + case LOONGARCH_HFLAG_BC: + case LOONGARCH_HFLAG_B: + ctx->btarget = env->btarget; + break; + } +} + +void generate_exception_err(DisasContext *ctx, int excp, int err) +{ + TCGv_i32 texcp = tcg_const_i32(excp); + TCGv_i32 terr = tcg_const_i32(err); + save_cpu_state(ctx, 1); + gen_helper_raise_exception_err(cpu_env, texcp, terr); + tcg_temp_free_i32(terr); + tcg_temp_free_i32(texcp); + ctx->base.is_jmp = DISAS_NORETURN; +} + +void generate_exception_end(DisasContext *ctx, int excp) +{ + generate_exception_err(ctx, excp, 0); +} + +void gen_reserved_instruction(DisasContext *ctx) +{ + generate_exception_end(ctx, EXCP_INE); +} + +void gen_load_fpr32(TCGv_i32 t, int reg) +{ + tcg_gen_extrl_i64_i32(t, fpu_f64[reg]); +} + +void gen_store_fpr32(TCGv_i32 t, int reg) +{ + TCGv_i64 t64 = tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(t64, t); + tcg_gen_deposit_i64(fpu_f64[reg], fpu_f64[reg], t64, 0, 32); + tcg_temp_free_i64(t64); +} + +static void gen_load_fpr32h(TCGv_i32 t, int reg) +{ + tcg_gen_extrh_i64_i32(t, fpu_f64[reg]); +} + +static void gen_store_fpr32h(TCGv_i32 t, int reg) +{ + TCGv_i64 t64 = tcg_temp_new_i64(); + tcg_gen_extu_i32_i64(t64, t); + tcg_gen_deposit_i64(fpu_f64[reg], fpu_f64[reg], t64, 32, 32); + tcg_temp_free_i64(t64); +} + +void gen_load_fpr64(TCGv_i64 t, int reg) +{ + tcg_gen_mov_i64(t, fpu_f64[reg]); +} + +void gen_store_fpr64(TCGv_i64 t, int reg) +{ + tcg_gen_mov_i64(fpu_f64[reg], t); +} + +void gen_op_addr_add(TCGv ret, TCGv arg0, TCGv arg1) +{ + tcg_gen_add_tl(ret, arg0, arg1); +} + +void check_fpu_enabled(DisasContext *ctx) +{ + /* Nop */ +} + +/* + * This code generates a "reserved instruction" exception if 64-bit + * instructions are not enabled. + */ +void check_loongarch_64(DisasContext *ctx) +{ + if (unlikely(!(ctx->hflags & LOONGARCH_HFLAG_64))) { + gen_reserved_instruction(ctx); + } +} + +void gen_base_offset_addr(TCGv addr, int base, int offset) +{ + if (base == 0) { + tcg_gen_movi_tl(addr, offset); + } else if (offset == 0) { + gen_load_gpr(addr, base); + } else { + tcg_gen_movi_tl(addr, offset); + gen_op_addr_add(addr, cpu_gpr[base], addr); + } +} + + +static inline bool use_goto_tb(DisasContext *ctx, target_ulong dest) +{ + return true; +} + +static inline void gen_goto_tb(DisasContext *ctx, int n, target_ulong dest) +{ + if (use_goto_tb(ctx, dest)) { + tcg_gen_goto_tb(n); + gen_save_pc(dest); + tcg_gen_exit_tb(ctx->base.tb, n); + } else { + gen_save_pc(dest); + tcg_gen_lookup_and_goto_ptr(); + } +} + +static inline void clear_branch_hflags(DisasContext *ctx) +{ + ctx->hflags &= ~LOONGARCH_HFLAG_BMASK; + if (ctx->base.is_jmp == DISAS_NEXT) { + save_cpu_state(ctx, 0); + } else { + /* + * It is not safe to save ctx->hflags as hflags may be changed + * in execution time. + */ + tcg_gen_andi_i32(hflags, hflags, ~LOONGARCH_HFLAG_BMASK); + } +} + +static void gen_branch(DisasContext *ctx, int insn_bytes) +{ + if (ctx->hflags & LOONGARCH_HFLAG_BMASK) { + int proc_hflags = ctx->hflags & LOONGARCH_HFLAG_BMASK; + /* Branches completion */ + clear_branch_hflags(ctx); + ctx->base.is_jmp = DISAS_NORETURN; + switch (proc_hflags & LOONGARCH_HFLAG_BMASK) { + case LOONGARCH_HFLAG_B: + /* unconditional branch */ + gen_goto_tb(ctx, 0, ctx->btarget); + break; + case LOONGARCH_HFLAG_BC: + /* Conditional branch */ + { + TCGLabel *l1 = gen_new_label(); + + tcg_gen_brcondi_tl(TCG_COND_NE, bcond, 0, l1); + gen_goto_tb(ctx, 1, ctx->base.pc_next + insn_bytes); + gen_set_label(l1); + gen_goto_tb(ctx, 0, ctx->btarget); + } + break; + case LOONGARCH_HFLAG_BR: + /* unconditional branch to register */ + tcg_gen_mov_tl(cpu_PC, btarget); + tcg_gen_lookup_and_goto_ptr(); + break; + default: + fprintf(stderr, "unknown branch 0x%x\n", proc_hflags); + abort(); + } + } +} + +static void loongarch_tr_init_disas_context(DisasContextBase *dcbase, + CPUState *cs) +{ + DisasContext *ctx = container_of(dcbase, DisasContext, base); + CPULoongArchState *env = cs->env_ptr; + + ctx->page_start = ctx->base.pc_first & TARGET_PAGE_MASK; + ctx->saved_pc = -1; + ctx->btarget = 0; + /* Restore state from the tb context. */ + ctx->hflags = (uint32_t)ctx->base.tb->flags; + restore_cpu_state(env, ctx); + ctx->mem_idx = LOONGARCH_HFLAG_UM; + ctx->default_tcg_memop_mask = MO_UNALN; +} + +static void loongarch_tr_tb_start(DisasContextBase *dcbase, CPUState *cs) +{ +} + +static void loongarch_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) +{ + DisasContext *ctx = container_of(dcbase, DisasContext, base); + + tcg_gen_insn_start(ctx->base.pc_next, ctx->hflags & LOONGARCH_HFLAG_BMASK, + ctx->btarget); +} + +static bool loongarch_tr_breakpoint_check(DisasContextBase *dcbase, + CPUState *cs, + const CPUBreakpoint *bp) +{ + return true; +} + +static void loongarch_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) +{ + CPULoongArchState *env = cs->env_ptr; + DisasContext *ctx = container_of(dcbase, DisasContext, base); + int insn_bytes = 4; + + ctx->opcode = cpu_ldl_code(env, ctx->base.pc_next); + + if (!decode(ctx, ctx->opcode)) { + fprintf(stderr, "Error: unkown opcode. 0x%lx: 0x%x\n", + ctx->base.pc_next, ctx->opcode); + generate_exception_end(ctx, EXCP_INE); + } + + if (ctx->hflags & LOONGARCH_HFLAG_BMASK) { + gen_branch(ctx, insn_bytes); + } + ctx->base.pc_next += insn_bytes; +} + +static void loongarch_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs) +{ + DisasContext *ctx = container_of(dcbase, DisasContext, base); + + switch (ctx->base.is_jmp) { + case DISAS_STOP: + gen_save_pc(ctx->base.pc_next); + tcg_gen_lookup_and_goto_ptr(); + break; + case DISAS_NEXT: + case DISAS_TOO_MANY: + save_cpu_state(ctx, 0); + gen_goto_tb(ctx, 0, ctx->base.pc_next); + break; + case DISAS_EXIT: + tcg_gen_exit_tb(NULL, 0); + break; + case DISAS_NORETURN: + break; + default: + g_assert_not_reached(); + } +} + +static void loongarch_tr_disas_log(const DisasContextBase *dcbase, CPUState *cs) +{ + qemu_log("IN: %s\n", lookup_symbol(dcbase->pc_first)); + log_target_disas(cs, dcbase->pc_first, dcbase->tb->size); +} + +static const TranslatorOps loongarch_tr_ops = { + .init_disas_context = loongarch_tr_init_disas_context, + .tb_start = loongarch_tr_tb_start, + .insn_start = loongarch_tr_insn_start, + .breakpoint_check = loongarch_tr_breakpoint_check, + .translate_insn = loongarch_tr_translate_insn, + .tb_stop = loongarch_tr_tb_stop, + .disas_log = loongarch_tr_disas_log, +}; + +void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns) +{ + DisasContext ctx; + + translator_loop(&loongarch_tr_ops, &ctx.base, cs, tb, max_insns); +} + +static void fpu_dump_state(CPULoongArchState *env, FILE * f, int flags) +{ + int i; + int is_fpu64 = 1; + +#define printfpr(fp) \ + do { \ + if (is_fpu64) \ + qemu_fprintf(f, "w:%08x d:%016" PRIx64 \ + " fd:%13g fs:%13g psu: %13g\n", \ + (fp)->w[FP_ENDIAN_IDX], (fp)->d, \ + (double)(fp)->fd, \ + (double)(fp)->fs[FP_ENDIAN_IDX], \ + (double)(fp)->fs[!FP_ENDIAN_IDX]); \ + else { \ + fpr_t tmp; \ + tmp.w[FP_ENDIAN_IDX] = (fp)->w[FP_ENDIAN_IDX]; \ + tmp.w[!FP_ENDIAN_IDX] = ((fp) + 1)->w[FP_ENDIAN_IDX]; \ + qemu_fprintf(f, "w:%08x d:%016" PRIx64 \ + " fd:%13g fs:%13g psu:%13g\n", \ + tmp.w[FP_ENDIAN_IDX], tmp.d, \ + (double)tmp.fd, \ + (double)tmp.fs[FP_ENDIAN_IDX], \ + (double)tmp.fs[!FP_ENDIAN_IDX]); \ + } \ + } while (0) + + + qemu_fprintf(f, + "FCSR0 0x%08x SR.FR %d fp_status 0x%02x\n", + env->active_fpu.fcsr0, is_fpu64, + get_float_exception_flags(&env->active_fpu.fp_status)); + for (i = 0; i < 32; (is_fpu64) ? i++ : (i += 2)) { + qemu_fprintf(f, "%3s: ", fregnames[i]); + printfpr(&env->active_fpu.fpr[i]); + } + +#undef printfpr +} + +void loongarch_cpu_dump_state(CPUState *cs, FILE *f, int flags) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(cs); + CPULoongArchState *env = &cpu->env; + int i; + + qemu_fprintf(f, "pc=0x" TARGET_FMT_lx " ds %04x " + TARGET_FMT_lx " " TARGET_FMT_ld "\n", + env->active_tc.PC, env->hflags, env->btarget, env->bcond); + for (i = 0; i < 32; i++) { + if ((i & 3) == 0) { + qemu_fprintf(f, "GPR%02d:", i); + } + qemu_fprintf(f, " %s " TARGET_FMT_lx, + regnames[i], env->active_tc.gpr[i]); + if ((i & 3) == 3) { + qemu_fprintf(f, "\n"); + } + } + + qemu_fprintf(f, "EUEN 0x%lx\n", env->CSR_EUEN); + qemu_fprintf(f, "ESTAT 0x%lx\n", env->CSR_ESTAT); + qemu_fprintf(f, "ERA 0x%lx\n", env->CSR_ERA); + qemu_fprintf(f, "CRMD 0x%lx\n", env->CSR_CRMD); + qemu_fprintf(f, "PRMD 0x%lx\n", env->CSR_PRMD); + qemu_fprintf(f, "BadVAddr 0x%lx\n", env->CSR_BADV); + qemu_fprintf(f, "TLB refill ERA 0x%lx\n", env->CSR_TLBRERA); + qemu_fprintf(f, "TLB refill BadV 0x%lx\n", env->CSR_TLBRBADV); + qemu_fprintf(f, "EEPN 0x%lx\n", env->CSR_EEPN); + qemu_fprintf(f, "BadInstr 0x%lx\n", env->CSR_BADI); + qemu_fprintf(f, "PRCFG1 0x%lx\nPRCFG2 0x%lx\nPRCFG3 0x%lx\n", + env->CSR_PRCFG1, env->CSR_PRCFG3, env->CSR_PRCFG3); + if ((flags & CPU_DUMP_FPU) && (env->hflags & LOONGARCH_HFLAG_FPU)) { + fpu_dump_state(env, f, flags); + } +} + +void loongarch_tcg_init(void) +{ + int i; + + for (i = 0; i < 32; i++) + cpu_gpr[i] = tcg_global_mem_new(cpu_env, + offsetof(CPULoongArchState, + active_tc.gpr[i]), + regnames[i]); + + for (i = 0; i < 32; i++) { + int off = offsetof(CPULoongArchState, active_fpu.fpr[i].d); + fpu_f64[i] = tcg_global_mem_new_i64(cpu_env, off, fregnames[i]); + } + + cpu_PC = tcg_global_mem_new(cpu_env, + offsetof(CPULoongArchState, + active_tc.PC), "PC"); + bcond = tcg_global_mem_new(cpu_env, + offsetof(CPULoongArchState, bcond), "bcond"); + btarget = tcg_global_mem_new(cpu_env, + offsetof(CPULoongArchState, btarget), + "btarget"); + hflags = tcg_global_mem_new_i32(cpu_env, + offsetof(CPULoongArchState, hflags), + "hflags"); + fpu_fcsr0 = tcg_global_mem_new_i32(cpu_env, + offsetof(CPULoongArchState, + active_fpu.fcsr0), "fcsr0"); + cpu_lladdr = tcg_global_mem_new(cpu_env, + offsetof(CPULoongArchState, lladdr), + "lladdr"); + cpu_llval = tcg_global_mem_new(cpu_env, + offsetof(CPULoongArchState, llval), + "llval"); +} + +void restore_state_to_opc(CPULoongArchState *env, TranslationBlock *tb, + target_ulong *data) +{ + env->active_tc.PC = data[0]; + env->hflags &= ~LOONGARCH_HFLAG_BMASK; + env->hflags |= data[1]; + switch (env->hflags & LOONGARCH_HFLAG_BMASK) { + case LOONGARCH_HFLAG_BR: + break; + case LOONGARCH_HFLAG_BC: + case LOONGARCH_HFLAG_B: + env->btarget = data[2]; + break; + } +} diff --git a/target/loongarch/translate.h b/target/loongarch/translate.h new file mode 100644 index 0000000..333c3bf --- /dev/null +++ b/target/loongarch/translate.h @@ -0,0 +1,49 @@ +/* + * LoongArch translation routines. + * + * Copyright (c) 2021 Loongson Technology Corporation Limited + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#ifndef TARGET_LOONGARCH_TRANSLATE_H +#define TARGET_LOONGARCH_TRANSLATE_H + +#include "exec/translator.h" + +#define LOONGARCH_DEBUG_DISAS 0 + +typedef struct DisasContext { + DisasContextBase base; + target_ulong saved_pc; + target_ulong page_start; + uint32_t opcode; + /* Routine used to access memory */ + int mem_idx; + MemOp default_tcg_memop_mask; + uint32_t hflags, saved_hflags; + target_ulong btarget; +} DisasContext; + +void generate_exception_err(DisasContext *ctx, int excp, int err); +void generate_exception_end(DisasContext *ctx, int excp); +void gen_reserved_instruction(DisasContext *ctx); + +void check_insn(DisasContext *ctx, uint64_t flags); +void check_loongarch_64(DisasContext *ctx); +void check_fpu_enabled(DisasContext *ctx); + +void gen_base_offset_addr(TCGv addr, int base, int offset); +void gen_load_gpr(TCGv t, int reg); +void gen_load_fpr32(TCGv_i32 t, int reg); +void gen_load_fpr64(TCGv_i64 t, int reg); +void gen_store_fpr32(TCGv_i32 t, int reg); +void gen_store_fpr64(TCGv_i64 t, int reg); +void gen_op_addr_add(TCGv ret, TCGv arg0, TCGv arg1); + +extern TCGv cpu_gpr[32], cpu_PC; +extern TCGv_i32 fpu_fscr0; +extern TCGv_i64 fpu_f64[32]; +extern TCGv bcond; + +#endif
This patch add main translation routines and basic functions for translation. Signed-off-by: Song Gao <gaosong@loongson.cn> --- target/loongarch/helper.h | 10 + target/loongarch/op_helper.c | 27 +++ target/loongarch/translate.c | 485 +++++++++++++++++++++++++++++++++++++++++++ target/loongarch/translate.h | 49 +++++ 4 files changed, 571 insertions(+) create mode 100644 target/loongarch/helper.h create mode 100644 target/loongarch/op_helper.c create mode 100644 target/loongarch/translate.c create mode 100644 target/loongarch/translate.h