Binary Translation型のRISC-Vエミュレータを作っている。現在のTCGは、レジスタを直接指定するのではなくアロケートする仕組みになっている。このアロケーション処理をもう少し簡単に記述できるようにするため、Rustのビットマップの機能を使ってみた。
Rustのビットマップは以下のようなものだ。
レジスタの数だけビットマップを持っておき、アロケートすると順番にレジスタを消費する。
まず、Bitmapは初期値ですべてのビットをTrueに設定しておく。これはBitmapがfind_first()
という最初のTrueのビットを探す関数しか持っていないため、最初にすべてTrueにしておき順番に消費するという流れになっている。
pub fn new() -> TranslateRiscv { let mut trans = TranslateRiscv { reg_bitmap: Bitmap::new() }; for idx in 0..5 { trans.reg_bitmap.set(idx, true); } trans }
まず、レジスタを消費するためにtcg_temp_new()
を実行する。
let src_addr = self.tcg_temp_new(); let vaddr_low12bit = self.tcg_temp_new(); let vaddr_tlb_idx = self.tcg_temp_new(); let stack_reg = self.tcg_temp_new(); let tlb_byte_addr = self.tcg_temp_new();
これによりBitmapがアサインされ自動的に使用するレジスタが決定される。
pub fn tcg_temp_new(&mut self) -> TCGv { let new_idx = match self.reg_bitmap.first_index() { Some(idx) => { self.reg_bitmap.set(idx, false); idx } None => panic!("New temporaries not found."), }; let new_v = TCGv::new_temp(new_idx as u64); new_v }
一方でtcg_temp_free()
を実行するとそのレジスタは解放される。
self.tcg_temp_free(vaddr_low12bit); self.tcg_temp_free(vaddr_tlb_idx ); self.tcg_temp_free(tlb_byte_addr ); self.tcg_temp_free(rs2_data);
以下のようにBitmapを解放する。
pub fn tcg_temp_free(&mut self, idx: TCGv) { self.reg_bitmap.set(idx.value as usize, true); }
x86においてどのレジスタを使用するかは、以下の関数で決定する。
fn convert_x86_reg(temp: u64) -> X86TargetRM { return match temp { 0 => X86TargetRM::RDX, 1 => X86TargetRM::RBX, 2 => X86TargetRM::RCX, 3 => X86TargetRM::RSI, 4 => X86TargetRM::RDI, 5 => X86TargetRM::SIB, _ => panic!("Not supported yet") } }
これでx86の可能な限りのレジスタをアサインすることができるようになった。また、TCG自体はレジスタの存在を気にする必要がなくなった。