Rustで作る自作命令セットシミュレータの続き。残りの命令を実装していく。次はアトミック命令の実装だ。これもC++で実装したことがあるのでRustに移植するだけである。
Rustでの実装で気をつけなければならないのは型の変換を明示的に行わなければならないこと。 型の変換をあいまいにしていたC++の実装では、気が付かない暗黙的な変換もRustでは気をつけなければならない。
今回Rustに移植したのは以下の命令だ。32ビットと64ビットが存在する。 現在はLRとSCの実装は省略してある。
C++では以下のように実装していたものを、Rustに移植する。
void InstEnv::RISCV_INST_AMOXOR_W (InstWord_t inst_hex) { RegAddr_t rs1_addr = ExtractR1Field (inst_hex); RegAddr_t rs2_addr = ExtractR2Field (inst_hex); RegAddr_t rd_addr = ExtractRDField (inst_hex); DWord_t rs1_val = m_pe_thread->ReadGReg<UDWord_t> (rs1_addr); UWord_t rs2_val = m_pe_thread->ReadGReg<Word_t> (rs2_addr); Addr_t mem_addr = m_pe_thread->UExtXlen (rs1_val); MemResult except; Word_t mem; // 例外処理。ブログの転載は省略。 Word_t ret = mem ^ rs2_val; except = m_pe_thread->StoreToBus (mem_addr, ret); // 例外処理。ブログの転載は省略。 m_pe_thread->WriteGReg (rd_addr, mem); }
これをRustに移植する。以下のようになった。ほとんどC++版と一緒だ。
pub trait Riscv64InstsAmo { fn execute_amoswap_w (&mut self, inst: InstT); fn execute_amoadd_w (&mut self, inst: InstT); fn execute_amoxor_w (&mut self, inst: InstT); fn execute_amoand_w (&mut self, inst: InstT); ...
fn execute_amoadd_w (&mut self, inst: InstT) { let rs1 = Self::get_rs1_addr(inst); let rs2 = Self::get_rs2_addr(inst); let rd = Self::get_rd_addr(inst); let rs1_data_64 = self.read_reg(rs1); let rs2_data_64 = self.read_reg(rs2); let mem_addr = self.uext_xlen(rs1_data_64) as Addr64T; let rs2_data = Self::extend_sign(rs2_data_64, 31); match self.read_bus_word(mem_addr) { Ok(mem_data_64) => { let mem_data = Self::extend_sign(mem_data_64, 31); let ret = mem_data.wrapping_add(rs2_data); self.write_bus_word(mem_addr, ret as Xlen64T); self.write_reg(rd, mem_data); }, Err(_result) => {}, } }
メモリアクセスはResult型で返されるのでその判定をmatchで行い、それ以外は型を拡張させながら同じように実装する。
という訳で、これをすべてのバリエーションで実装した。改めてリグレッションテストを実行し、問題ないことを確認した。良さそうだ。