Binary Translation型のエミュレータをRustで作るプロジェクト、ある程度ベンチマークテストも動くようになってきたので、次なる大物はCompressed命令のサポートだろう。Compressed命令はRISC-Vの命令系列の中でも16ビット長のもので、一般的な32ビットの命令と異なりフェッチ幅を削減するために使用されている。
この命令については、C++で開発したRISC-V命令セットシミュレータではサポートしている。C++のシミュレータでは、デコーダは自動生成させたのでこちらも対応しているはずだ。これをRustに置き換えて実装する。
その結果、現状の32ビットの命令系列の生成に加えて、16ビット長の命令もデコードできるように拡張した。
fn decode_inst_ld_01_f3_001_r3_00000_f2_00_r2_00000_r1_00001 (inst: u32) -> Option<(RiscvInstId, usize)> { let field_rd = ((inst as u64) >> 7) & (((1 as u64) << 5) - 1); return match field_rd { 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 => Some((RiscvInstId::C_SRLI64, 2)), 0x08 | 0x09 | 0x0a | 0x0b | 0x0c | 0x0d | 0x0e | 0x0f => Some((RiscvInstId::C_SRAI64, 2)), 0x10 | 0x11 | 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 => Some((RiscvInstId::C_ANDI, 2)), 0x18 | 0x19 | 0x1a | 0x1b | 0x1c | 0x1d | 0x1e | 0x1f => // Remaining Instruction is 2 // c.subw cr[9:7],r[6:2] // c.addw cr[9:7],r[6:2] decode_inst_ld_01_f3_001_r3_00000_f2_00_r2_00000_r1_00001_rd_11000 (inst), _ => None, } } fn decode_inst_ld_01_f3_001_r3_00000_f2_00_r2_00000_r1_00001_rd_11000 (inst: u32) -> Option<(RiscvInstId, usize)> { let field_op = ((inst as u64) >> 2) & (((1 as u64) << 5) - 1); return match field_op { 0x00 | 0x01 | 0x02 | 0x03 | 0x04 | 0x05 | 0x06 | 0x07 => Some((RiscvInstId::C_SUBW, 2)), 0x08 | 0x09 | 0x0a | 0x0b | 0x0c | 0x0d | 0x0e | 0x0f => Some((RiscvInstId::C_ADDW, 2)), _ => None, } }
これまで命令デコーダはデコード結果の命令IDを返すだけだったが、これに加えて命令バイト長も返すようにした。これにより次に進めるべきPCの値を設定できる。
とりあえず1つ目の命令として、C.ADDI4SPN
命令を実装してみた。
pub fn translate_c_addi4spn(&mut self, inst: &InstrInfo) -> Vec<TCGOp> { let imm_const: u64 = get_nzuimm!(inst.inst as i32); let rs1_addr= 2; // sp let rd_addr = get_c_rd_addr!((inst.inst >> 2) & 0x7); let mut tcg_lists = vec![]; if rd_addr == 0 { return vec![]; } let source1 = self.tcg_temp_new(); tcg_lists.push(TCGOp::tcg_get_gpr(source1, rs1_addr)); tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, source1, source1, TCGv::new_imm(imm_const))); tcg_lists.push(TCGOp::tcg_set_gpr(rd_addr, source1)); self.tcg_temp_free(source1); tcg_lists }
こんな感じで中身は単なる加算命令なので、加算命令のTCG生成を使用しつつ即値フィールドの切り出し方法と、レジスタのデコード方法を切り替えるようにした。
この結果、以下のように命令の実行が確認できた。
========= BLOCK START ========= 50: Guest PC Address = 80002016 0000000080002016:0000000080002016 Hostcode 13b71fe8 : c.addi4spn a0, sp, 1020 00007F967D5B0000 488B9518000000 mov 0x18(%rbp),%rdx 00007F967D5B0007 4881C2FC030000 add $0x3FC,%rdx 00007F967D5B000E 48899558000000 mov %rdx,0x58(%rbp) 00007F967D5B0015 E9F5FF2B00 jmp 0x0000_7F96_7D87_000F x00(zero ) = 0000000000000000 x01(ra ) = 0000000000000000 x02(sp ) = 0000000000001234 x03(gp ) = 0000000000000002 x04(tp ) = 0000000000000000 x05(t0 ) = 00000000800000fc x06(t1 ) = 0000000000000000 x07(t2 ) = 000000000000029b x08(s0/fp) = 0000000000000000 x09(s1 ) = 0000000000000000 x10(a0 ) = 0000000000001630 x11(a1 ) = 000000000000029b x12(a2 ) = 0000000000000000 x13(a3 ) = 0000000000000000 x14(a4 ) = 0000000000000000 x15(a5 ) = 0000000000000000 x16(a6 ) = 0000000000000000 x17(a7 ) = 0000000000000000 x18(s2 ) = 0000000000000000 x19(s3 ) = 0000000000000000 x20(s4 ) = 0000000000000000 x21(s5 ) = 0000000000000000 x22(s6 ) = 0000000000000000 x23(s7 ) = 0000000000000000 x24(s8 ) = 0000000000000000 x25(s9 ) = 0000000000000000 x26(s10 ) = 0000000000000000 x27(s11 ) = 0000000000000000 x28(t3 ) = 0000000000000000 x29(t4 ) = 0000000000000000 x30(t5 ) = 0000000000000000 x31(t6 ) = 0000000000000000
フレームワークは一通り完成したので、今後はこれにサポート命令を増やしていく作業に入る。