Binary Translation方式のエミュレータ開発、RISC-Vの基本命令については一通り実装が完了した。次はどちらの方向に進むかということだが、いくつか考えられるのは、
- 浮動小数点命令のサポート
- 仮想アドレスのサポート
- Compressed 命令のサポート
- AArch64ホストマシンのサポート
とりあえずまずは浮動小数点命令を試そう。QEMUを調査して、浮動小数点命令の実行にはsoftfloatライブラリの力を利用していることが分かった。これを使えば簡単に浮動小数点命令のサポートができそうだ(実際自作RISC-Vシミュレータの開発にはsoftfloatを使っていた)。
しかしまずは浮動小数点のロードストア命令から実装すべきだろう。ロードストア命令は単純なメモリアクセスなので、softfloatを使用する必要はない。まずはRISC-Vの浮動小数点ロードストア命令から実装して行く。
最初に、浮動小数点レジスタを用意した。以下のm_fregs
がそれに当たる。Double Sizeまでサポートするためにu64型のレジスタを32本用意した。
pub struct EmuEnv { pub head: [u64; 1], // pointer of this struct. Do not move. m_regs: [u64; 32], // Integer Registers m_fregs: [u64; 32], // Floating Point Registers m_pc: [u64; 1], ...
サポート用に、ホストの命令からm_fregs
までの距離を計算するための相対距離計算ルーチンを用意しておく。これは浮動小数点レジスタにアクセスする際、ベースレジスタからのオフセットとして使用する。
pub fn calc_fregs_relat_address(&self, gpr_addr: u64) -> isize { let guestcode_ptr = self.m_fregs.as_ptr() as *const u8; let self_ptr = self.head.as_ptr() as *const u8; let mut diff = unsafe { guestcode_ptr.offset_from(self_ptr) }; diff += gpr_addr as isize * mem::size_of::<u64>() as isize; diff }
では、RISC-Vの命令を定義する。今回定義するのはFLD, FLW, FSD, FSW
の4命令だ。
pub fn translate(id: RiscvInstId, inst: &InstrInfo) -> Vec<TCGOp> { return match id { RiscvInstId::ADDI => TranslateRiscv::translate_addi(inst), RiscvInstId::ADD => TranslateRiscv::translate_add(inst), ... RiscvInstId::FLD => TranslateRiscv::translate_fld(inst), RiscvInstId::FLW => TranslateRiscv::translate_flw(inst), RiscvInstId::FSD => TranslateRiscv::translate_fsd(inst), RiscvInstId::FSW => TranslateRiscv::translate_fsw(inst),
それぞれのtranslate_xxx
関数では、3オペランドの演算実行用TCGを定義する。これは本当はもっと細かなTCGに分解した方が良い気がするが、現在はざっくりとしたTCGになってしまっている。
pub fn translate_fld(inst: &InstrInfo) -> Vec<TCGOp> { Self::translate_float_rri(TCGOpcode::LOAD_FLOAT_64BIT, inst) } pub fn translate_flw(inst: &InstrInfo) -> Vec<TCGOp> { Self::translate_float_rri(TCGOpcode::LOAD_FLOAT_32BIT, inst) } pub fn translate_fsd(inst: &InstrInfo) -> Vec<TCGOp> { Self::translate_float_rri(TCGOpcode::STORE_FLOAT_64BIT, inst) } pub fn translate_fsw(inst: &InstrInfo) -> Vec<TCGOp> { Self::translate_float_rri(TCGOpcode::STORE_FLOAT_32BIT, inst) }
なぜ浮動小数点命令用に特殊なtranslate_float_rri()
という関数を用意したかというと、通常のtranslate_rri()
はゼロレジスタの書き込みを禁止してしまうので。translate_float_rri()
はゼロレジスタへの書き込みを許可している。
fn translate_float_rri(op: TCGOpcode, inst: &InstrInfo) -> Vec<TCGOp> { let rs1_addr: usize = get_rs1_addr!(inst.inst) as usize; let imm_const: u64 = ((inst.inst as i32) >> 20) as u64; let rd_addr: usize = get_rd_addr!(inst.inst) as usize; let rs1 = Box::new(TCGv::new_reg(rs1_addr as u64)); let imm = Box::new(TCGv::new_imm(imm_const)); let rd = Box::new(TCGv::new_reg(rd_addr as u64)); let tcg_inst = TCGOp::new_3op(op, *rd, *rs1, *imm); return vec![tcg_inst] }
次はこのTCGをx86命令に変換する処理だ。これは実は整数ロードストア命令とほとんど変わらない。書き込み先・読み込み先が浮動小数点レジスタに置き換わるだけである。
/* Memory Access : Load */ fn tcg_gen_load( emu: &EmuEnv, pc_address: u64, tcg: &TCGOp, mc: &mut Vec<u8>, mem_size: MemOpType, target_reg: RegisterType, ) -> usize { let mut gen_size: usize = pc_address as usize; ... // Store Loaded value into destination register. if target_reg == RegisterType::IntRegister { gen_size += Self::tcg_gen_store_gpr_64bit(emu, X86TargetRM::RAX, arg0.value, mc); } else if target_reg == RegisterType::FloatRegister && mem_size == MemOpType::LOAD_64BIT { gen_size += Self::tcg_gen_store_fregs_64bit(emu, X86TargetRM::RAX, arg0.value, mc); } else if target_reg == RegisterType::FloatRegister && mem_size == MemOpType::LOAD_32BIT { gen_size += Self::tcg_gen_store_fregs_32bit(emu, X86TargetRM::RAX, arg0.value, mc); } else { panic!("Unknown condition for Register Write") } return gen_size;
もし浮動小数点レジスタへをターゲットとしているならば、tcg_gen_store_fregs_64bit()
もしくはtcg_gen_store_fregs_32bit()
を呼び出す。これは先ほど定義した浮動小数点レジスタへのベースポインタからの相対距離を利用して値を書き込む関数だ。
fn tcg_gen_store_fregs_64bit( emu: &EmuEnv, source: X86TargetRM, dest: u64, mc: &mut Vec<u8>, ) -> usize { let mut gen_size = 0; gen_size += Self::tcg_modrm_64bit_out(X86Opcode::MOV_EV_GV, X86ModRM::MOD_10_DISP_RBP, source, mc); gen_size += Self::tcg_out(emu.calc_fregs_relat_address(dest) as u64, 4, mc); return gen_size; }
ストア命令も同様だ。読み込み元のレジスタを浮動小数点レジスタに置き換えるだけである。
/* Memory Access : Store */ fn tcg_gen_store( emu: &EmuEnv, pc_address: u64, tcg: &TCGOp, mc: &mut Vec<u8>, mem_size: MemOpType, target_reg: RegisterType ) -> usize { let mut gen_size: usize = pc_address as usize; ... // Load value from rs1(addr) if target_reg == RegisterType::IntRegister { gen_size += Self::tcg_gen_load_gpr_64bit(emu, X86TargetRM::RAX, arg0.value, mc); } else if target_reg == RegisterType::FloatRegister && mem_size == MemOpType::LOAD_64BIT { gen_size += Self::tcg_gen_load_fregs_64bit(emu, X86TargetRM::RAX, arg0.value, mc); } else if target_reg == RegisterType::FloatRegister && mem_size == MemOpType::LOAD_32BIT { gen_size += Self::tcg_gen_load_fregs_32bit(emu, X86TargetRM::RAX, arg0.value, mc); } else { panic!("Unknown Register read condition.") }