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によりエミュレートされているようだった。