QEMUの続き。独自ターゲットでビルドしてみる。static const TranslatorOps myriscvx_tr_ops
で必要な関数を調査する。
必要な関数を確認しながら追加していく。myriscvx_tr_breakpoint_check()
の中で、デバッグ例外を発生させることが必要だ。
static bool myriscvx_tr_breakpoint_check(DisasContextBase *dcbase, CPUState *cpu, const CPUBreakpoint *bp) { DisasContext *ctx = container_of(dcbase, DisasContext, base); ... gen_exception_debug(); ... }
static void gen_exception_debug(void) { TCGv_i32 helper_tmp = tcg_const_i32(EXCP_DEBUG); gen_helper_raise_exception(cpu_env, helper_tmp); tcg_temp_free_i32(helper_tmp); }
EXCP_DEBUG
はCPU2(helper_tmp);
}
EXCP_DEBUGはCPU全体で定義されている例外のようだ。qemu/include/exec/cpu-all.h
で定義がされている。
qemu/include/exec/cpu-all.h
#define EXCP_INTERRUPT 0x10000 /* async interruption */ #define EXCP_HLT 0x10001 /* hlt instruction reached */ #define EXCP_DEBUG 0x10002 /* cpu stopped after a breakpoint or singlestep */ #define EXCP_HALTED 0x10003 /* cpu is halted (waiting for external event) */ #define EXCP_YIELD 0x10004 /* cpu wants to yield timeslice to another */ #define EXCP_ATOMIC 0x10005 /* stop-the-world and emulate atomic */
gen_helper_raise_exception(cpu_env, helper_tmp)
はどこで定義すれば良いんだろう?
qemu/target/riscv/helper.h
/* Exceptions */ DEF_HELPER_2(raise_exception, noreturn, env, i32) // --> #define DEF_HELPER_2(name, ret, t1, t2) \ DEF_HELPER_FLAGS_2(name, 0, ret, t1, t2) // --> #define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \ static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) \ dh_arg_decl(t1, 1), dh_arg_decl(t2, 2)) \ { \ TCGTemp *args[2] = { dh_arg(t1, 1), dh_arg(t2, 2) }; \ tcg_gen_callN(HELPER(name), dh_retvar(ret), 2, args); \ }
次はdecode_opc()
を定義しなければならないらしい。
/home/msyksphinz/work/riscv/qemu/target/myriscvx/translate.c: In function 'myriscvx_tr_translate_insn': /home/msyksphinz/work/riscv/qemu/target/myriscvx/translate.c:117:3: error: implicit declaration of function 'decode_opc'; did you mean 'g_node_copy'? [-Werror=implicit-function-declaration] decode_opc(env, ctx, opcode16); ^~~~~~~~~~
RISC-Vのdecode_opc()
では、まず16ビット命令としてのデコードをチェックし、そうでなければ32ビット命令としてデコード、それでもだめならば命令デコード例外として出力する。
static void decode_opc(CPURISCVState *env, DisasContext *ctx, uint16_t opcode) { /* check for compressed insn */ if (extract16(opcode, 0, 2) != 3) { if (!has_ext(ctx, RVC)) { gen_exception_illegal(ctx); } else { ctx->pc_succ_insn = ctx->base.pc_next + 2; if (!decode_insn16(ctx, opcode)) { /* fall back to old decoder */ decode_RV32_64C(ctx, opcode); } } } else { uint32_t opcode32 = opcode; opcode32 = deposit32(opcode32, 16, 16, translator_lduw(env, ctx->base.pc_next + 2)); ctx->pc_succ_insn = ctx->base.pc_next + 4; if (!decode_insn32(ctx, opcode32)) { gen_exception_illegal(ctx); } } }
ここでdeposit32()
マクロは定数の一部フィールドを修飾するためのマクロで、translator_lduw()
はメモリロード命令の共通ラッパーのようなものらしい。これはそもそもQEMUが16ビット単位でしか命令をロードしていないために、もう一度ロードして32ビット命令を作っている、ということなのか?そしてdecode_insn32
に渡して命令のデコードを行っているということになる。
そしてどうやらこのdecode_insn32()
は自動的に生成されるものらしい。
qemu/build-myriscvx/riscv64-softmmu/target/riscv/decode_insn32.inc.c
static bool decode_insn32(DisasContext *ctx, uint32_t insn) { union { arg_atomic f_atomic; arg_b f_b; arg_decode_insn3210 f_decode_insn3210; arg_decode_insn3211 f_decode_insn3211; ... switch (insn & 0x0000007f) { case 0x00000003: /* ........ ........ ........ .0000011 */ decode_insn32_extract_i(ctx, &u.f_i, insn); switch ((insn >> 12) & 0x7) { case 0x0: /* ........ ........ .000.... .0000011 */ /* /home/msyksphinz/work/riscv/qemu/target/riscv/insn32.decode:96 */ if (trans_lb(ctx, &u.f_i)) return true; return false; case 0x1: /* ........ ........ .001.... .0000011 */ /* /home/msyksphinz/work/riscv/qemu/target/riscv/insn32.decode:97 */ if (trans_lh(ctx, &u.f_i)) return true; ...
/home/msyksphinz/work/riscv/qemu/target/riscv/insn32.decode
# *** RV32I Base Instruction Set *** lui .................... ..... 0110111 @u auipc .................... ..... 0010111 @u jal .................... ..... 1101111 @j jalr ............ ..... 000 ..... 1100111 @i beq ....... ..... ..... 000 ..... 1100011 @b bne ....... ..... ..... 001 ..... 1100011 @b blt ....... ..... ..... 100 ..... 1100011 @b ...
これもDSLか。。。なかなか大変だな。とりあえず先に進めると、trans_lb
やtrans_lh
などの命令は地味に主導で実装しなければならないらしい。
qemu/target/riscv/insn_trans/trans_rvi.inc.c
static bool trans_lb(DisasContext *ctx, arg_lb *a) { return gen_load(ctx, a, MO_SB); } static bool trans_lh(DisasContext *ctx, arg_lh *a) { return gen_load(ctx, a, MO_TESW); }
static bool gen_load(DisasContext *ctx, arg_lb *a, MemOp memop) { TCGv t0 = tcg_temp_new(); TCGv t1 = tcg_temp_new(); gen_get_gpr(t0, a->rs1); tcg_gen_addi_tl(t0, t0, a->imm); tcg_gen_qemu_ld_tl(t1, t0, ctx->mem_idx, memop); gen_set_gpr(a->rd, t1); tcg_temp_free(t0); tcg_temp_free(t1); return true; }
tcg_temp_new()
などのメソッドはすべてQEMUの共通関数となっている。こうすることによってよりx86ネイティブコードに近い命令を生成しているということなんだろうか?
objdump
でtranslate.o
をダンプしてみても、例えば直接trans_add
などの関数は見つけられなかった。ただしtrans_flt_s
などの関数はそのまま関数が見えている。trans_fadd
などはインライン展開されて消えちゃったのかな?
0000000000000b70 <trans_flt_s>: b70: 55 push %rbp b71: 53 push %rbx b72: 48 83 ec 28 sub $0x28,%rsp b76: 8b 57 40 mov 0x40(%rdi),%edx b79: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax b80: 00 00 b82: 48 89 44 24 18 mov %rax,0x18(%rsp) b87: 31 c0 xor %eax,%eax ...