FPGA開発日記

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

Binary Translation型エミュレータを作る(ロード命令を新しいTCGで書き直す)

Binary Translation型のRISC-Vエミュレータを作っている。これまで、Binary Translationのための中間表現であるTCGは、かなりx86に近い構造になっていたが、より一般的な構造としてTCGをより細かな粒度に変換している。新しいTCGを使ってメモリアクセス命令を書き直していこうと思う。

まずはロード命令について考える。ロード命令については、以下のようなフローを取る。

  1. レジスタと即値を加算して仮想アドレスを作成する
  2. 仮想アドレスを用いてTLBを検索し、TLBがヒットするかを確認する
  3. TLBがヒットすれば、TLBの情報に基づいて物理アドレスを計算する
  4. 物理アドレスに基づいてメモリアクセスを行う
  5. (2. においてヒットしなければ、)ヘルパー関数を呼び出して仮想アドレスから物理アドレスを計算しメモリアクセスを行う
f:id:msyksphinz:20201031110627p:plain

この方針に基づいてロード命令を実装した。以下のようになる。

       let rs1_addr = get_rs1_addr!(inst.inst);
        let imm_const: u64 = ((inst.inst as i32) >> 20) as u64;
        let rd_addr = get_rd_addr!(inst.inst);

        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();

        let label_tlb_match = Rc::new(RefCell::new(TCGLabel::new()));
        let tcg_label_tlb_match = TCGOp::new_label(Rc::clone(&label_tlb_match));

        let mut tcg_lists = vec![];

        // Read Register
        tcg_lists.push(TCGOp::tcg_get_gpr(src_addr, rs1_addr));
        // Extract TLB Index and offset
        if imm_const != 0 {
            tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, src_addr, src_addr, TCGv::new_imm(imm_const)));
        }
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::AND_64BIT, vaddr_low12bit, src_addr, TCGv::new_imm(0xfff)));

        tcg_lists.push(TCGOp::new_3op(TCGOpcode::SRL_64BIT, vaddr_tlb_idx, src_addr, TCGv::new_imm(12)));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::AND_64BIT, vaddr_tlb_idx, vaddr_tlb_idx, TCGv::new_imm(0xfff)));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::SLL_64BIT, vaddr_tlb_idx, vaddr_tlb_idx, TCGv::new_imm(3)));

        // Make TLB Vaddr Index Address
        tcg_lists.push(TCGOp::new_1op(TCGOpcode::MOVE_STACK, stack_reg));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::ADD_TLBIDX_OFFSET, tlb_byte_addr, stack_reg));  // Relative Addr of TLB
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, vaddr_tlb_idx));

        // Make VAddr upper bit for compare TLB value
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::SRL_64BIT, src_addr, src_addr, TCGv::new_imm(24)));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::MEM_LOAD, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::new_2op_with_label(TCGOpcode::CMP_EQ, src_addr, tlb_byte_addr, Rc::clone(&label_tlb_match)));
        // if TLB not hit, jump helper function
        tcg_lists.push(TCGOp::new_helper_call_arg4(CALL_HELPER_IDX::CALL_LOAD64_IDX as usize, 
                                                   TCGv::new_reg(rd_addr as u64), 
                                                   TCGv::new_reg(rs1_addr as u64), 
                                                   TCGv::new_imm(imm_const), 
                                                   TCGv::new_imm(inst.addr)));

        let zero = Box::new(TCGv::new_reg(0 as u64));
        let dummy_addr = Box::new(TCGv::new_imm(0));
        
        let label_load_excp = Rc::new(RefCell::new(TCGLabel::new()));
        let tcg_label_load_excp = TCGOp::new_label(Rc::clone(&label_load_excp));

        tcg_lists.push(TCGOp::new_4op(TCGOpcode::EQ_EAX_64BIT, src_addr, *zero, *dummy_addr, Rc::clone(&label_load_excp)));
        tcg_lists.push(TCGOp::new_0op(TCGOpcode::EXIT_TB, None));

        // Extract lower 12bit address and add with TLB address
        tcg_lists.push(tcg_label_tlb_match);
        tcg_lists.push(TCGOp::new_1op(TCGOpcode::MOVE_STACK, stack_reg));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::ADD_TLBADDR_OFFSET, tlb_byte_addr, stack_reg));  // Relative Addr of TLB Paddr
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, vaddr_tlb_idx));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::MEM_LOAD, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, vaddr_low12bit));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, TCGv::new_imm(0x80000000)));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::ADD_MEM_OFFSET, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::MEM_LOAD, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::tcg_set_gpr(rd_addr, tlb_byte_addr));
        tcg_lists.push(tcg_label_load_excp);

        self.tcg_temp_free(src_addr      );
        self.tcg_temp_free(vaddr_low12bit);
        self.tcg_temp_free(vaddr_tlb_idx );
        self.tcg_temp_free(stack_reg     );
        self.tcg_temp_free(tlb_byte_addr );

        return tcg_lists;

一つずつ噛み砕いていく。

  • まず、レジスタを読み出してアクセスしたい仮想アドレスを計算する。
       let rs1_addr = get_rs1_addr!(inst.inst);
        let imm_const: u64 = ((inst.inst as i32) >> 20) as u64;
        let rd_addr = get_rd_addr!(inst.inst);

        // レジスタ読み込みのためのTCGを挿入する
        tcg_lists.push(TCGOp::tcg_get_gpr(src_addr, rs1_addr));
        // 即値オフセットが0でなければ即値を加算するTCGを挿入する
        if imm_const != 0 {
            tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, src_addr, src_addr, TCGv::new_imm(imm_const)));
        }
  • TLBのアドレスを計算して、TLBへのアクセスを行う。
       // 仮想アドレスの[23:12]ビットを抽出してしてTLBアクセスのアドレスを算出する
        // TLBのアドレスエントリは64ビットなので、アドレスとして算出するために最後に3ビット左にシフトする
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::SRL_64BIT, vaddr_tlb_idx, src_addr, TCGv::new_imm(12)));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::AND_64BIT, vaddr_tlb_idx, vaddr_tlb_idx, TCGv::new_imm(0xfff)));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::SLL_64BIT, vaddr_tlb_idx, vaddr_tlb_idx, TCGv::new_imm(3)));

        // TLBの存在しているベースアドレスを計算し、上記のTLBオフセットを加算しアドレスを決定する
        tcg_lists.push(TCGOp::new_1op(TCGOpcode::MOVE_STACK, stack_reg));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::ADD_TLBIDX_OFFSET, tlb_byte_addr, stack_reg));  // Relative Addr of TLB
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, vaddr_tlb_idx));
  • TLBアクセスを行い、TLBのアドレスと対象アドレスが一致すればTLBを用いてメモリアクセスを行うパスにジャンプ、そうでなければヘルパー関数へジャンプ
        // Make VAddr upper bit for compare TLB value
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::SRL_64BIT, src_addr, src_addr, TCGv::new_imm(24)));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::MEM_LOAD, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::new_2op_with_label(TCGOpcode::CMP_EQ, src_addr, tlb_byte_addr, Rc::clone(&label_tlb_match)));
  • ヘルパー関数の実装:仮想アドレスから物理アドレスへの変換、TLBへのアドレス登録を行う
            // if TLB not hit, jump helper function
            tcg_lists.push(TCGOp::new_helper_call_arg4(CALL_HELPER_IDX::CALL_LOAD64_IDX as usize, 
                                                    TCGv::new_reg(rd_addr as u64), 
                                                    TCGv::new_reg(rs1_addr as u64), 
                                                    TCGv::new_imm(imm_const), 
                                                    TCGv::new_imm(inst.addr)));

            let zero = Box::new(TCGv::new_reg(0 as u64));
            let dummy_addr = Box::new(TCGv::new_imm(0));
            
            let label_load_excp = Rc::new(RefCell::new(TCGLabel::new()));
            let tcg_label_load_excp = TCGOp::new_label(Rc::clone(&label_load_excp));

            tcg_lists.push(TCGOp::new_4op(TCGOpcode::EQ_EAX_64BIT, src_addr, *zero, *dummy_addr, Rc::clone(&label_load_excp)));
            tcg_lists.push(TCGOp::new_0op(TCGOpcode::EXIT_TB, None));
  • TLBを用いたアドレスの生成とメモリアクセスの実行。TLBから再度物理アドレスを取得し、オフセットを加算して物理アドレスとしてメモリアドレスを生成
        // Extract lower 12bit address and add with TLB address
        tcg_lists.push(tcg_label_tlb_match);
        tcg_lists.push(TCGOp::new_1op(TCGOpcode::MOVE_STACK, stack_reg));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::ADD_TLBADDR_OFFSET, tlb_byte_addr, stack_reg));  // Relative Addr of TLB Paddr
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, vaddr_tlb_idx));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::MEM_LOAD, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, vaddr_low12bit));
        tcg_lists.push(TCGOp::new_3op(TCGOpcode::ADD_64BIT, tlb_byte_addr, tlb_byte_addr, TCGv::new_imm(0x80000000)));
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::ADD_MEM_OFFSET, tlb_byte_addr, tlb_byte_addr));
  • メモリアクセスを行い、結果をレジスタに格納する
        tcg_lists.push(TCGOp::new_2op(TCGOpcode::MEM_LOAD, tlb_byte_addr, tlb_byte_addr));
        tcg_lists.push(TCGOp::tcg_set_gpr(rd_addr, tlb_byte_addr));

結果的に生成されたx86命令は以下のようになった。

54: Guest PC Address = 80000130
<Convert_Virtual_Address. virtual_addr=0000000080000130 : vm_mode = 0, priv_mode = 3>
  converted physical address = 80000130
 0000000080000130:0000000080000130 Hostcode 0080bf03 : ld      t5, 8(ra)
00007FA8CF9C0000 488B9510000000       mov       0x10(%rbp),%rdx
00007FA8CF9C0007 4881C208000000       add       $8,%rdx
00007FA8CF9C000E 488BDA               mov       %rdx,%rbx
00007FA8CF9C0011 4881E3FF0F0000       and       $0xFFF,%rbx
00007FA8CF9C0018 488BCA               mov       %rdx,%rcx
00007FA8CF9C001B 48C1E90C             shr       $0xC,%rcx
00007FA8CF9C001F 4881E1FF0F0000       and       $0xFFF,%rcx
00007FA8CF9C0026 48C1E103             shl       $3,%rcx
00007FA8CF9C002A 488BF5               mov       %rbp,%rsi
00007FA8CF9C002D 488BFE               mov       %rsi,%rdi
00007FA8CF9C0030 4881C7A0050000       add       $0x5A0,%rdi
00007FA8CF9C0037 4803F9               add       %rcx,%rdi
00007FA8CF9C003A 48C1EA18             shr       $0x18,%rdx
00007FA8CF9C003E 488B3F               mov       (%rdi),%rdi
00007FA8CF9C0041 483BFA               cmp       %rdx,%rdi
00007FA8CF9C0044 0F844A000000         je        0x0000_7FA8_CF9C_0094
00007FA8CF9C004A 48BF90957FFEFF7F0000 movabs    $0x7FFF_FE7F_9590,%rdi
00007FA8CF9C0054 48BE1E00000000000000 movabs    $0x1E,%rsi
00007FA8CF9C005E 48BA0100000000000000 movabs    $1,%rdx
00007FA8CF9C0068 48B90800000000000000 movabs    $8,%rcx
00007FA8CF9C0072 49B83001008000000000 movabs    $0x8000_0130,%r8
00007FA8CF9C007C FF9560040000         callq     *0x460(%rbp)
00007FA8CF9C0082 483B8508000000       cmp       8(%rbp),%rax
00007FA8CF9C0089 0F840A000000         je        0x0000_7FA8_CF9C_0099
00007FA8CF9C008F E97BFF2F00           jmp       0x0000_7FA8_CFCC_000F
00007FA8CF9C0094 488BF5               mov       %rbp,%rsi
00007FA8CF9C0097 488BFE               mov       %rsi,%rdi
00007FA8CF9C009A 4881C7A0850000       add       $0x85A0,%rdi
00007FA8CF9C00A1 4803F9               add       %rcx,%rdi
00007FA8CF9C00A4 488B3F               mov       (%rdi),%rdi
00007FA8CF9C00A7 4803FB               add       %rbx,%rdi
00007FA8CF9C00AA 4881C700000080       add       $0xFFFF_FFFF_8000_0000,%rdi
00007FA8CF9C00B1 48B80000CDCFA87F0000 movabs    $0x7FA8_CFCD_0000,%rax
00007FA8CF9C00BB 4803F8               add       %rax,%rdi
00007FA8CF9C00BE 488B3F               mov       (%rdi),%rdi
00007FA8CF9C00C1 4889BDF8000000       mov       %rdi,0xF8(%rbp)
00007FA8CF9C00C8 E942FF2F00           jmp       0x0000_7FA8_CFCC_000F