FPGA開発日記

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

Binary Translation型エミュレータを作る(fsgnj命令の実装)

Binary Translation方式のエミュレータをRustで作る実装、浮動小数点命令を実装している。次はfsgnj命令の実装だ。fsgnj命令は単精度・倍精度を合わせて合計6つ存在している。

FSGNJ.D      fd, fs1, fs2        // f[rd] = {f[rs2][63], f[rs1][62:0]}
FSGNJN.D    fd, fs1, fs2        // f[rd] = {~f[rs2][63], f[rs1][62:0]}
FSGNJX.D    fd, fs1, fs2        // f[rd] = {f[rs2][63] ^ f[rs2][63], f[rs1][62:0]}
FSGNJ.S     fd, fs1, fs2        // f[rd] = {f[rs2][31], f[rs1][30:0]}
FSGNJN.S    fd, fs1, fs2        // f[rd] = {~f[rs2][31], f[rs1][30:0]}
FSGNJX.S    fd, fs1, fs2        // f[rd] = {f[rs1][31] ^ f[rs2][31], f[rs1][30:0]}

倍精度のFSGNJ命令については、頑張ってすべてTCGで実装した。

    fn tcg_gen_sgnj_64bit(emu: &EmuEnv, pc_address: u64, tcg: &TCGOp, mc: &mut Vec<u8>) -> usize {
        let op = tcg.op.unwrap();
        let arg0 = tcg.arg0.unwrap();
        let arg1 = tcg.arg1.unwrap();
        let arg2 = tcg.arg2.unwrap();

        assert_eq!(op, TCGOpcode::SGNJ_64BIT);
        assert_eq!(arg0.t, TCGvType::Register);
        assert_eq!(arg1.t, TCGvType::Register);
        assert_eq!(arg2.t, TCGvType::Register);

        let mut gen_size: usize = pc_address as usize;

        gen_size += Self::tcg_gen_imm_u64(X86TargetRM::RCX, 0x7fffffff_ffffffff, mc);
        gen_size += Self::tcg_modrm_64bit_out(X86Opcode::AND_GV_EV, X86ModRM::MOD_10_DISP_RBP, X86TargetRM::RCX, mc);
        gen_size += Self::tcg_out(emu.calc_fregs_relat_address(arg1.value) as u64, 4, mc);

        gen_size += Self::tcg_gen_imm_u64(X86TargetRM::RAX, 0x80000000_00000000, mc);
        gen_size += Self::tcg_modrm_64bit_out(X86Opcode::AND_GV_EV, X86ModRM::MOD_10_DISP_RBP, X86TargetRM::RAX, mc);
        gen_size += Self::tcg_out(emu.calc_fregs_relat_address(arg2.value) as u64, 4, mc);

        gen_size += Self::tcg_modrm_64bit_out(X86Opcode::OR_GV_EV, X86ModRM::MOD_11_DISP_RCX, X86TargetRM::RAX, mc);
        gen_size += Self::tcg_gen_store_fregs_64bit(emu, X86TargetRM::RAX, arg0.value, mc);

        return gen_size;
    }

このように実装すると、SGNJ.DX86命令で驚異的な短さで実装できる。

0x4001cfe0b8:  48 b9 ff ff ff ff ff ff  movabsq  $0x7fffffffffffffff, %rcx
0x4001cfe0c0:  ff 7f
0x4001cfe0c2:  48 23 8d 10 01 00 00     andq     0x110(%rbp), %rcx
0x4001cfe0c9:  48 b8 00 00 00 00 00 00  movabsq  $0x8000000000000000, %rax
0x4001cfe0d1:  00 80
0x4001cfe0d3:  48 23 85 18 01 00 00     andq     0x118(%rbp), %rax
0x4001cfe0da:  48 0b c1                 orq      %rcx, %rax
0x4001cfe0dd:  48 89 85 08 01 00 00     movq     %rax, 0x108(%rbp)

で、これを32ビット版で同じように実装するとどうもテストパタンが落ちてしまうので調べていると、32ビット版の場合はNaN Boxingを適用しなければならないのだった。

    #[inline]
    fn convert_nan_boxing (i: u64) -> u32 {
        if i & 0xffffffff_00000000 == 0xffffffff_00000000 {
            (i & 0xffffffff) as u32
        } else {
            0x7fc00000
        }
    }

ちょっと面倒くさいなー、と思い、結局NaN Boxingも含めて32ビット版はRustで実装してしまった。。。でもよく考えると、TCGもラベルジャンプを実装していることだし、全部TCGで実装できないことはなさそうだな。。。一応テストパタンは通ったけど、最適化として一応考えてみようかな。

    pub fn helper_func_fsgnj_s(emu: &mut EmuEnv, rd: u32, fs1: u32, fs2: u32, _dummy: u32) -> usize {
        let fs1_data = Self::convert_nan_boxing(emu.m_fregs[fs1 as usize]) as u32;
        let fs2_data = Self::convert_nan_boxing(emu.m_fregs[fs2 as usize]) as u32;
        let mut flag = ExceptionFlags::default();
        flag.set();
        emu.m_fregs[rd as usize] = (fs1_data & 0x7fffffff | fs2_data & 0x80000000) as u64 | 0xffffffff_00000000;
        flag.get();
        let ret_flag = flag.bits();
        println!("fsgnj_s(emu, {:}, {:}, {:}) is called!", rd, fs1, fs2);
        emu.m_csr.csrrw(CsrAddr::FFlags, ret_flag as i64);
        return 0;
    }