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.D
がX86命令で驚異的な短さで実装できる。
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; }