FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

QEMUに入門してみる(5. translateに必要な関数の確認)

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_lbtrans_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ネイティブコードに近い命令を生成しているということなんだろうか?

objdumptranslate.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
...