FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Binary Translation型エミュレータを作る(レジスタ自動アサインを考える2)

前回のレジスタ自動アサインでとりあえず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ビットに符号拡張を行うので、MOVSLQTCGを挿入する。

    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