前回のレジスタ自動アサインでとりあえず2レジスタの加算はできるようになったが、次は1レジスタと即値をもつ命令の生成方法を考える。TCGのオペレーション自体は変えたくないので、source
オペランドの属性だけ変えて、命令生成時に属性によって生成する命令を切り替えることにしよう。1レジスタと1即値を持つ命令の場合は以下のような生成ルーチンを持たせている。
new_get_gpr()
を1回呼び出して1つ分のレジスタを確保し、2つ目のオペランドは即値属性を持たせてTCGを生成する。
pub fn translate_rri(&mut self, op: TCGOpcode, inst: &InstrInfo) -> Vec<TCGOp> { let rs1_addr= get_rs1_addr!(inst.inst); let rd_addr = get_rd_addr!(inst.inst); let imm_const: u64 = ((inst.inst as i32) >> 20) as u64; let tcg_imm = TCGv::new_imm(imm_const); if rd_addr == 0 { return vec![]; } let source1 = self.tcg_temp_new(); let rs1_op = TCGOp::new_get_gpr(source1, rs1_addr); // Box::new(TCGv::new_reg(rs1_addr as u64)); let tcg_inst = TCGOp::new_3op(op, source1, source1, tcg_imm); let rd_op = TCGOp::new_set_gpr(rd_addr, source1); // Box::new(TCGv::new_reg(rs1_addr as u64)); self.tcg_temp_free(source1); vec![rs1_op, tcg_inst, rd_op] }
そして2番目のオペランドが即値属性だった場合を条件付けして命令を生成する。
fn tcg_gen_add_64bit(emu: &EmuEnv, pc_address: u64, tcg: &tcg::TCGOp, mc: &mut Vec<u8>) -> usize { if tcg.arg2.unwrap().t == TCGvType::Immediate { Self::tcg_gen_op_temp_imm(pc_address, X86Opcode::ADD_GV_IMM, tcg, mc) } else { Self::tcg_gen_op_temp(pc_address, X86Opcode::ADD_GV_EV, tcg, mc) } }
fn tcg_gen_op_temp_imm(pc_address: u64, op: X86Opcode, tcg: &tcg::TCGOp, mc: &mut Vec<u8>) -> usize { let dest_reg = tcg.arg0.unwrap(); let source1_reg = tcg.arg1.unwrap(); let source2_imm = tcg.arg2.unwrap(); assert_eq!(dest_reg.t, TCGvType::TCGTemp); assert_eq!(source1_reg.t, TCGvType::TCGTemp); assert_eq!(source2_imm.t, TCGvType::Immediate); let mut gen_size: usize = pc_address as usize; let source1_x86reg = Self::convert_x86_reg(source1_reg.value); gen_size += Self::tcg_modrm_64bit_raw_out(op, X86ModRM::MOD_11_DISP_RAX as u8 + source1_x86reg as u8, 0, mc); gen_size += Self::tcg_out(source2_imm.value, 4, mc); gen_size }
レジスタ+レジスタの場合はADD_GV_IMM
命令を使用し、レジスタ+即値の場合はADD_GV_EV
命令を使用する。これで対応は完了だ。ADDI
命令の命令実行の様子は以下のようになる。
0000000080000058:0000000080000058 Hostcode 01028293 : addi t0, t0, 16
00007FA470F40000 488B9530000000 mov 0x30(%rbp),%rdx 00007FA470F40007 4881C210000000 add $0x10,%rdx 00007FA470F4000E 48899530000000 mov %rdx,0x30(%rbp) 00007FA470F40015 E9F5FF0200 jmp 0x0000_7FA4_70F7_000F
もう一つ、ADDIW
命令の場合はもう一工夫必要となる。32ビット演算を行った後上位32ビットに符号拡張を行うので、MOVSLQ
のTCGを挿入する。
pub fn translate_addiw(&mut self, inst: &InstrInfo) -> Vec<TCGOp> { let rs1_addr= get_rs1_addr!(inst.inst); let rd_addr = get_rd_addr!(inst.inst); let imm_const: u64 = ((inst.inst as i32) >> 20) as u64; let tcg_imm = TCGv::new_imm(imm_const); if rd_addr == 0 { return vec![]; } let source1 = self.tcg_temp_new(); let rs1_op = TCGOp::new_get_gpr(source1, rs1_addr); // Box::new(TCGv::new_reg(rs1_addr as u64)); let tcg_inst = TCGOp::new_3op(TCGOpcode::ADD_32BIT, source1, source1, tcg_imm); // SIGN_EXT_32_64を挿入しているのがミソ let tcg_sign_ext = TCGOp::new_2op(TCGOpcode::SIGN_EXT_32_64, source1, source1); let rd_op = TCGOp::new_set_gpr(rd_addr, source1); // Box::new(TCGv::new_reg(rs1_addr as u64)); self.tcg_temp_free(source1); vec![rs1_op, tcg_inst, tcg_sign_ext, rd_op] }
SIGN_EXT_32_64
は以下のように変換される。
fn tcg_gen_sign_ext_32_64(emu: &EmuEnv, pc_address: u64, tcg: &TCGOp, mc: &mut Vec<u8>) -> usize { let dest_reg = tcg.arg0.unwrap(); let source1_reg = tcg.arg1.unwrap(); assert_eq!(dest_reg.t, TCGvType::TCGTemp); assert_eq!(source1_reg.t, TCGvType::TCGTemp); let mut gen_size: usize = pc_address as usize; let dest_x86reg = Self::convert_x86_reg(dest_reg.value); let source1_x86reg = Self::convert_x86_reg(source1_reg.value); gen_size += Self::tcg_modrm_64bit_raw_out(X86Opcode::MOV_GV_EV_32BIT, X86ModRM::MOD_11_DISP_RAX as u8 + source1_x86reg as u8, dest_x86reg as u8, mc); gen_size }
MOV_GV_EV_32BIT
を用いて変換される。以下のようなx86命令が生成される。
0000000080000114:0000000080000114 Hostcode 00108f1b : addiw t5, ra, 1
00007FD4832E0000 488B9510000000 mov 0x10(%rbp),%rdx 00007FD4832E0007 81C201000000 add $1,%edx 00007FD4832E000D 4863D2 movslq %edx,%rdx 00007FD4832E0010 488995F8000000 mov %rdx,0xF8(%rbp) 00007FD4832E0017 E9F3FF2800 jmp 0x0000_7FD4_8357_000F