FPGA開発日記

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

QEMUはどのように浮動小数点命令をエミュレートしているのか

QEMUはゲストマシンの機械語をホストマシンの機械語に直接翻訳して(Binary Translation)実行することで高速動作を実現しているのだが、整数命令の場合はこれで問題ない。しかし浮動小数点命令の場合はどうだろう?RISC-Vとx86では浮動小数点命令の丸めの扱いなどが微妙に異なる。これをQEMUは正しく処理しているのだろうか?ソースコードを読んでみよう。

RISC-Vの浮動小数点命令

まずはRISC-Vの浮動小数点命令について調査してみたい。RISC-Vには単精度浮動小数点加算命令が用意されている。

 FADD.S rd,rs1,rs2

これがQEMUでデコードされてどのようにTCGが生成されるかについて確認した。

static bool trans_fadd_s(DisasContext *ctx, arg_fadd_s *a)
{
    REQUIRE_FPU;
    REQUIRE_EXT(ctx, RVF);

    gen_set_rm(ctx, a->rm);
    gen_helper_fadd_s(cpu_fpr[a->rd], cpu_env,
                      cpu_fpr[a->rs1], cpu_fpr[a->rs2]);
    mark_fs_dirty(ctx);
    return true;
}

やはりHelper関数を呼び出している。x86では厳密にRISC-Vの浮動小数点加算命令をエミュレートできないのだと思う(例外もあるしね)。従って浮動小数点演算のソフトウェア実装に丸投げをしている。

helper_fadd_s()はまんまsoftfloatを呼び出していた。

uint64_t helper_fadd_s(CPURISCVState *env, uint64_t frs1, uint64_t frs2)
{
    return float32_add(frs1, frs2, &env->fp_status);
}

ちなみに最後のmark_fs_dirty()RISC-VのMSTATUS.FSビットを設定するためのルーチンなのであまり気にする必要はない。

static void mark_fs_dirty(DisasContext *ctx)
{
    TCGv tmp;
    if (ctx->mstatus_fs == MSTATUS_FS) {
        return;
    }
    /* Remember the state change for the rest of the TB.  */
    ctx->mstatus_fs = MSTATUS_FS;

    tmp = tcg_temp_new();
    tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus));
    tcg_gen_ori_tl(tmp, tmp, MSTATUS_FS | MSTATUS_SD);
    tcg_gen_st_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus));

    if (ctx->virt_enabled) {
        tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus_hs));
        tcg_gen_ori_tl(tmp, tmp, MSTATUS_FS | MSTATUS_SD);
        tcg_gen_st_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus_hs));
    }
    tcg_temp_free(tmp);
}

x86浮動小数点命令

x86浮動小数点命令についてはあまり詳しくないのだが、x86のターゲットコードを見ていると、いくつかの命令定義を見つけた。

  • qemu/target/i386/translate.c
static void gen_helper_fp_arith_ST0_FT0(int op)
{
    switch (op) {
    case 0:
        gen_helper_fadd_ST0_FT0(cpu_env);
        break;
    case 1:
        gen_helper_fmul_ST0_FT0(cpu_env);
        break;
    case 2:
        gen_helper_fcom_ST0_FT0(cpu_env);
        break;
    case 3:
        gen_helper_fcom_ST0_FT0(cpu_env);
        break;
...

gen_helper_fadd_ST0_FT0()の実体はi386/fpu_helper.cに存在している。

  • qemu/target/i386/fpu_helper.c
void helper_fadd_ST0_FT0(CPUX86State *env)
{
    ST0 = floatx80_add(ST0, FT0, &env->fp_status);
}

このfloat80_add()の実体を追いかけるとこれもsoftfloatだった。結局どのターゲットでもsoftfloatを使っているということなんだろうか?

  • qemu/fpu/softfloat.c
floatx80 floatx80_add(floatx80 a, floatx80 b, float_status *status)
{
    flag aSign, bSign;

    if (floatx80_invalid_encoding(a) || floatx80_invalid_encoding(b)) {
        float_raise(float_flag_invalid, status);
        return floatx80_default_nan(status);
    }
    aSign = extractFloatx80Sign( a );
    bSign = extractFloatx80Sign( b );
    if ( aSign == bSign ) {
        return addFloatx80Sigs(a, b, aSign, status);
    }
    else {
        return subFloatx80Sigs(a, b, aSign, status);
    }

}

AArch64浮動小数点命令

念のためAArch64でも確認してみる。

  • qemu/target/arm/translate-a64.c
/* Floating-point data-processing (2 source) - single precision */
static void handle_fp_2src_single(DisasContext *s, int opcode,
                                  int rd, int rn, int rm)
{
    TCGv_i32 tcg_op1;
    TCGv_i32 tcg_op2;
    TCGv_i32 tcg_res;
    TCGv_ptr fpst;

    tcg_res = tcg_temp_new_i32();
    fpst = get_fpstatus_ptr(false);
    tcg_op1 = read_fp_sreg(s, rn);
    tcg_op2 = read_fp_sreg(s, rm);

    switch (opcode) {
    case 0x0: /* FMUL */
        gen_helper_vfp_muls(tcg_res, tcg_op1, tcg_op2, fpst);
        break;
    case 0x1: /* FDIV */
        gen_helper_vfp_divs(tcg_res, tcg_op1, tcg_op2, fpst);
        break;
    case 0x2: /* FADD */
        gen_helper_vfp_adds(tcg_res, tcg_op1, tcg_op2, fpst);
        break;
    case 0x3: /* FSUB */
...

こちらはVFPのFADD命令が呼び出されているようだ。そしてこれも。。。

  • qemu/target/arm/vfp_helper.c
#define VFP_BINOP(name) \
float32 VFP_HELPER(name, s)(float32 a, float32 b, void *fpstp) \
{ \
    float_status *fpst = fpstp; \
    return float32_ ## name(a, b, fpst); \
} \
float64 VFP_HELPER(name, d)(float64 a, float64 b, void *fpstp) \
{ \
    float_status *fpst = fpstp; \
    return float64_ ## name(a, b, fpst); \
}
VFP_BINOP(add)
VFP_BINOP(sub)
VFP_BINOP(mul)
...

float32_add()が呼び出されているということはこれもsoftfloatだ。結局すべてターゲットアーキテクチャにおける浮動小数点命令はsoftfloatによりエミュレートされているようだった。