FPGA開発日記

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

Binary Translation型エミュレータを作る(ロード命令の実装)

RustでBinary Translation型エミュレータを作っている。Binary Translation型エミュレータの特徴は、ゲストのバイナリ形式(RISC-Vなど)をホストのバイナリ形式(x86など)に実行時に変換するタイプのエミュレータである。今回はホストとしてはx86、ゲストとしてはRISC-Vを使用しており、RISC-Vの命令を等価なx86の命令に変換して実行することで、インタプリタ型のエミュレータ型と異なり高速実行を可能としている。

算術演算命令や分岐命令は簡単な実装が完了したので、次はメモリアクセス命令を実装してみる。まずはロード命令だ。ロード命令は基本的な流れとして、

  1. 汎用レジスタからベースとなるアドレスを取得する。
  2. ベースとなるアドレスに対して命令に付属されるオフセット値を加算しアドレスとする。
  3. アドレスに対してメモリロードを行い、値を取得する。
  4. 書き込み先の汎用レジスタに対してロードしたデータを書き込む。

となる。今回はMMUによるアドレス変換処理は無視することにしよう。QEMUにおけるアドレス変換方式は非常に構成が難しいのでそれは前の記事に纏めている。アドレス変換を考えなければ、ロード命令の実装は非常に単純である(といっても正しく動かすまで2日かけてしまった...)

私の実装したBinary Translationコンパイラの出力結果を見ながら、どのようにロード命令を実行しているのかをまとめる。この考え方はQEMUでも同じような方式で動いている。

まず、ゲストマシンはRISC-Vなので、以下のようなロード命令を考える。

LD    rd,imm(rs1)        // 64ビットロード命令
LW   rd,imm(rs2)        // 32ビット符号拡張ロード命令
LH   rd,imm(rs2)        // 16ビット符号拡張ロード命令
LB   rd,imm(rs2)        // 8ビット符号拡張ロード命令
LWU  rd,imm(rs2)        // 32ビット符号なしロード命令
LHU  rd,imm(rs2)        // 16ビット符号なしロード命令
LBU  rd,imm(rs2)        // 8ビット符号なしロード命令

それぞれの命令がどのようにx86コンパイルされるのかを見ていこう。

LD   x11,0(x10)      // 64ビットロード命令

0x4001c9501e:  48 8b 85 50 00 00 00     movq     0x50(%rbp), %rax
0x4001c95025:  48 01 d0                 addq     %rdx, %rax
0x4001c95028:  48 8b 80 00 00 00 00     movq     (%rax), %rax
0x4001c9502f:  48 89 85 58 00 00 00     movq     %rax, 0x58(%rbp)

最初のmovqが汎用レジスタからの値の取得に相当する。%rbpに対して0x50バイト加算した場所がx10の場所であり、そこから値を取得して%raxに格納する。次のaddqは、汎用レジスタの値に対してRISC-Vのバイナリが格納されているメモリ領域を加算し、物理メモリ上でロードすべき場所を計算する。

x86命令における3行目のmovqが実際のメモリアクセスに相当する。ここではオフセット値が0なので、オフセット=0としてメモリアドレスをロードしている。最後のmovqが汎用レジスタへのロードした値のストアになる。0x58はx11の場所であり、x11にロードした結果を格納している。

同じようにして、各サイズのロードアクセス命令が実装できる。

lw      x12, 4(x10)
0x4001c95040:  48 63 80 04 00 00 00     movslq   4(%rax), %rax

lh      x13, 6(x10)
0x4001c95058:  48 0f bf 80 06 00 00 00  movswq   6(%rax), %rax

lb      x14, 7(x10)
0x4001c95071:  48 0f be 80 07 00 00 00  movsbq   7(%rax), %rax

lwu     x20, 4(x10)
0x4001c950d4:  8b 80 04 00 00 00        movl     4(%rax), %eax

lhu     x21, 6(x10)
0x4001c950eb:  0f b7 80 06 00 00 00     movzwl   6(%rax), %eax

lbu     x22, 7(x10)
0x4001c95103:  0f b6 80 07 00 00 00     movzbl   7(%rax), %eax

以下のようなサンプルプログラムをコンパイルし、Binary Translationエミュレータで実行した結果を示すと以下のようになった。

    .section    .text

_start:
    lui     x10, %hi(data_region)
    addi    x10, x10, %lo(data_region)

    ld      x11, 0(x10)
    lw      x12, 4(x10)
    lh      x13, 6(x10)
    lb      x14, 7(x10)

    lw      x16, 0(x10)
    lh      x17, 0(x10)
    lb      x18, 0(x10)

    lwu     x20, 4(x10)
    lhu     x21, 6(x10)
    lbu     x22, 7(x10)

    lwu     x20, 0(x10)
    lhu     x21, 0(x10)
    lbu     x22, 0(x10)

    ret

#    .section    .data
data_region:
    .word   0xdeadbeef
    .word   0x01234567
x00 = 00007f7ae2c70000  x01 = 000000005085c748  x02 = 00007fffcb7d26a8  x03 = 0000000000000000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 0000000000000040  x11 = 01234567deadbeef
x12 = 0000000001234567  x13 = 0000000000000123  x14 = 0000000000000001  x15 = 0000000000000000
x16 = ffffffffdeadbeef  x17 = ffffffffffffbeef  x18 = ffffffffffffffef  x19 = 0000000000000000
x20 = 00000000deadbeef  x21 = 000000000000beef  x22 = 00000000000000ef  x23 = 0000000000000000
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000
PC = 0000000000000000

想定通りの結果だ。符号付き命令は符号拡張が行われ、そうでない命令は符号拡張せずにそのまま値が代入されている。

f:id:msyksphinz:20200903005811p:plain