Binary Translation方式の命令セットエミュレータのRust実装をしている。前回、ロード命令の実装をTLBに置き換えた。これによりTLBキャッシュにヒットした場合はロード命令の動作が高速化され、高速命令実行が可能になる。今度はこれをストア命令に対応させていきたい。ストア命令への対応もロード命令によるものと同じだ。仮想アドレスを計算した後にTLBの当該エントリにアクセスを行い、エントリアドレスが一致すればそのまま直接物理アドレスを生成し、その物理アドレスに対してデータをストアする。物理アドレスの生成方法はロード命令と同一だが、その後のデータストアだけの方式を変えれば良い。この方式を実装した。
こちらも生成したアセンブリ命令のみを掲載する。
0000000080002914:0000000080002914 Hostcode 6ed8b823 : sd a3, 1776(a7)
// 仮想アドレスを計算する。 rs1 + imm 00007F79744D0000 488B8590000000 mov 0x90(%rbp),%rax 00007F79744D0007 4881C0F0060000 add $0x6F0,%rax 00007F79744D000E 488BC8 mov %rax,%rcx // 仮想アドレスからTLB参照に使用する24~12ビット目を抽出する 00007F79744D0011 48C1E80C shr $0xC,%rax 00007F79744D0015 48C1E90C shr $0xC,%rcx 00007F79744D0019 4881E0FF0F0000 and $0xFFF,%rax 00007F79744D0020 48C1E003 shl $3,%rax 00007F79744D0024 488BD0 mov %rax,%rdx 00007F79744D0027 488BC5 mov %rbp,%rax 00007F79744D002A 48C1E90C shr $0xC,%rcx 00007F79744D002E 4881C088050000 add $0x588,%rax 00007F79744D0035 4803C2 add %rdx,%rax // TLBを検索して、ヒットするかどうかをチェックする 00007F79744D0038 483B08 cmp (%rax),%rcx // ヒットした場合、0x0000_7F79_744D_0090にジャンプする 00007F79744D003B 0F844F000000 je 0x0000_7F79_744D_0090 // TLBにヒットしなかった場合、引数を設定してヘルパー関数を呼び出す 00007F79744D0041 48BF9855A6F5FF7F0000 movabs $0x7FFF_F5A6_5598,%rdi 00007F79744D004B 48BE0D00000000000000 movabs $0xD,%rsi 00007F79744D0055 48BA1100000000000000 movabs $0x11,%rdx 00007F79744D005F 48B9F006000000000000 movabs $0x6F0,%rcx 00007F79744D0069 49B81429008000000000 movabs $0x8000_2914,%r8 00007F79744D0073 FF9598040000 callq *0x498(%rbp) // ヘルパー関数を呼び出した後の例外処理 00007F79744D0079 483B8508000000 cmp 8(%rbp),%rax 00007F79744D0080 0F840A000000 je 0x0000_7F79_744D_0090 00007F79744D0086 E984FF0800 jmp 0x0000_7F79_7456_000F 00007F79744D008B E97FFF0800 jmp 0x0000_7F79_7456_000F // ここから先がヘルパー関数を使用しないメモリアクセス 00007F79744D0090 48B800004E74797F0000 movabs $0x7F79_744E_0000,%rax 00007F79744D009A 488BC8 mov %rax,%rcx 00007F79744D009D 488B8590000000 mov 0x90(%rbp),%rax 00007F79744D00A4 4881C0F0060000 add $0x6F0,%rax 00007F79744D00AB 488BF0 mov %rax,%rsi 00007F79744D00AE 4881E6FF0F0000 and $0xFFF,%rsi 00007F79744D00B5 48C1E80C shr $0xC,%rax 00007F79744D00B9 4881E0FF0F0000 and $0xFFF,%rax 00007F79744D00C0 48C1E003 shl $3,%rax 00007F79744D00C4 488BD0 mov %rax,%rdx 00007F79744D00C7 488BC5 mov %rbp,%rax 00007F79744D00CA 4881C088850000 add $0x8588,%rax 00007F79744D00D1 4803C2 add %rdx,%rax // TLBアドレスリストから物理アドレスを取得する 00007F79744D00D4 488B00 mov (%rax),%rax 00007F79744D00D7 4803C6 add %rsi,%rax 00007F79744D00DA 4803C1 add %rcx,%rax 00007F79744D00DD 480500000080 add $0xFFFF_FFFF_8000_0000,%rax 00007F79744D00E3 488B8D70000000 mov 0x70(%rbp),%rcx // 物理アドレスを用いてメモリアクセスを実行 00007F79744D00EA 488908 mov %rcx,(%rax) 00007F79744D00ED E91DFF0800 jmp 0x0000_7F79_7456_000F
これでストア命令の実装は完了だ。実際にテストパタンを動かしてみよう。
$ cargo run -- --step --dump-host --dump-guest --dump-gpr --elf-file /home/msyksphinz/riscv64/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-v-sd
00007FCBEF1C00DA 4803C1 add %rcx,%rax 00007FCBEF1C00DD 480500000080 add $0xFFFF_FFFF_8000_0000,%rax 00007FCBEF1C00E3 488B8D58000000 mov 0x58(%rbp),%rcx 00007FCBEF1C00EA 488908 mov %rcx,(%rax) 00007FCBEF1C00ED E91DFF0800 jmp 0x0000_7FCB_EF25_000F label found 2 label found. offset = 8b replacement target is 82, data = 5 label found 2 label found. offset = 90 x00(zero ) = 0000000000000000 x01(ra ) = ffffffffffe028ac x02(sp ) = ffffffffffe0a660 x03(gp ) = 0000000000000017 x04(tp ) = 0000000000000002 x05(t0 ) = 0000000000000008 x06(t1 ) = 0000000000000000 x07(t2 ) = 0000000000000000 x08(s0/fp) = 000000000003f000 x09(s1 ) = 00000000000003e0 x10(a0 ) = 0000000000000001 x11(a1 ) = ffffffffffe05000 x12(a2 ) = 0000000000001000 x13(a3 ) = 0000000000005000 x14(a4 ) = 0000000000000000 x15(a5 ) = ffffffffffe01250 x16(a6 ) = 0000000000004000 x17(a7 ) = 0000000000000000 x18(s2 ) = 000000000003f000 x19(s3 ) = 0000000000000001 x20(s4 ) = ffffffffffe097e0 x21(s5 ) = ffffffffffe00000 x22(s6 ) = 0000000000040000 x23(s7 ) = ffffffffffe05000 x24(s8 ) = 0000000000000080 x25(s9 ) = 0000000000000000 x26(s10 ) = ffffffffffe093f0 x27(s11 ) = ffffffffffe04000 x28(t3 ) = 0000000000000000 x29(t4 ) = 0000000012233001 x30(t5 ) = 0000000012233001 x31(t6 ) = 0000000000000000 Result: MEM[0x1000] = 00000001
上手く行ったようだ。テストパタンは問題なくPASSすることが確認できた。TLBの参照回数をチェックしてみよう。
$cargo run -- --step --dump-host --dump-guest --dump-gpr --elf-file /home/msyksphinz/riscv64/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-v-sd | grep sd | wc
2670
$ cargo run -- --step --dump-host --dump-guest --dump-gpr --elf-file /home/msyksphinz/riscv64/riscv64-unknown-elf/share/riscv-tests/isa/rv64ui-v-sd | grep update | wc
45
2670回のメモリアクセス中で、TLBの更新が発生したのは45回だった。それ以外のメモリアクセス時にはヘルパー関数を使用せずにメモリアクセスに成功している。TLBの動作は、問題無いようだ。
SFENCE.VMAの実装
上記の実装ではTLBはいつまでもフラッシュされない。このためページてブルが書き換えられると問題が発生するため、ある一定条件でTLBをクリアしなければならない。SATPを更新するときか、SFENCE.VMAを更新するときか、マニュアルを参照してもはっきりとわからなかったので適当にSFENCE.VMAにTLBをフラッシュする命令を実装した。
ついでにSpikeの実装を見てみると、SFENCE.VMA
にTLBフラッシュが実装されていたからやはりここでいいのだろう。
riscv-isa-sim/riscv/insns/sfence_vma.h
require_extension('S');
require_privilege(get_field(STATE.mstatus, MSTATUS_TVM) ? PRV_M : PRV_S);
MMU.flush_tlb();
SFENCE.VMAはこれまで私のエミュレータでは全く実装が定義されていなかった命令ではあるが、ここに来てHelper関数を実装した。
pub fn helper_func_sfence_vma(emu: &mut EmuEnv, _dest: u64, _imm: u64, _csr_addr: u64, _dummy: u64) -> usize { // Clear TLB for idx in 0..4096 { emu.m_tlb_vec[idx] = 0xdeadbeef_01234567; } return 0; }
これでTLBが正しく更新されるようになった。