FPGA開発日記

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

Binary Translation型エミュレータを作る(64ビット演算への拡張)

Binary Translation型エミュレータを作っている。ゲストマシンはRISC-Vで、ホストマシンはx86である。ADDI命令は簡単なものであれば実装できるようになった(ソースレジスタがx0の場合に限る)。どんどん実装を進めていこう。次はLUI命令の問題だ。現在x86命令は32ビット命令として実装されており、即値の生成などに問題がある。

_start:
    lui    x1, 0x12345
    lui    x2, 0x87654
    add    x3, x1, x2
    fn tcg_modrm_out(op: X86Opcode, modrm: X86ModRM, mc: &mut Vec<u8>) {
        Self::tcg_out(((modrm as u32) << 8) | (op as u32), 3, mc);
    }

Mod/RMの設定の機械語生成は上記の通りだ。これでは、現状ではすべてのオペランドは32ビットとして取り扱われている。したがって、上記の機械語を実行すると以下のようになってしまい、誤った結果を生じる(32ビット以上に値が拡張されない)。

x00 = 0000000000000000  x01 = 0000000012345000  x02 = 0000000087654000  x03 = 0000000099999000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 0000000000000000  x11 = 0000000000000000
x12 = 0000000000000000  x13 = 0000000000000000  x14 = 0000000000000000  x15 = 0000000000000000
x16 = 0000000000000000  x17 = 0000000000000000  x18 = 0000000000000000  x19 = 0000000000000000
x20 = 0000000000000000  x21 = 0000000000000000  x22 = 0000000000000000  x23 = 0000000000000000
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000

では、64ビットのデータを取り扱うためにはどうしたらいいのか。C言語のプログラムを作って確認する。

int32_t add(int32_t a, int32_t *b)
{
  return a + *b;
}
0000000000000020 <add_64>:
  20:   48 8b 86 90 01 00 00    mov    0x190(%rsi),%rax
  27:   48 03 87 90 01 00 00    add    0x190(%rdi),%rax
  2e:   c3                      retq

うーん、これは何だろう?0x48というのが分からん。しかし色々調べてみると、0x48というのはMod/RMの拡張でREX.Wを設定する命令らしい。

https://www.wdic.org/w/SCI/REX%E3%83%97%E3%83%AA%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E3%82%B9

ロングモードでは、64ビット化するにあたり、16ビット時代から存在した1バイトのinc/dec命令であるinc reg16/dec reg16命令(op 0x40〜0x4f)を廃止してこの位置にプリフィックスを再定義した。結果として、4ビット分のビットフィールドが確保された。

なるほど、inc/dec命令を廃止してその代わりにPrefixを挿入したのか。これなら納得がいく。

7 6 5 4 3 2 1 0
0 1 0 0 REX.w REX.r REX.x REX.b
  • REX.w ‐ 1=オペランドサイズを64ビットにする。
  • REX.r ‐ ModR/Mのregフィールドのビット4として機能する。
  • REX.x ‐ SIBバイトのindexフィールドのビット4として機能する。
  • REX.b ‐ ModR/Mのr/mフィールドや、SIBのbaseフィールドのビット4として機能する。

これを追加することで演算単位がすべて64ビットになる。

    fn tcg_modrm_out(op: X86Opcode, modrm: X86ModRM, mc: &mut Vec<u8>) {
        Self::tcg_out(((modrm as u32) << 16) | (op as u32) << 8 | 0x48, 3, mc);
    }

これで先ほどのLUI命令を再度エミュレータで実行してみる。

x00 = 0000000000000000  x01 = 0000000012345000  x02 = ffffffff87654000  x03 = ffffffff99999000
x04 = 0000000000000000  x05 = 0000000000000000  x06 = 0000000000000000  x07 = 0000000000000000
x08 = 0000000000000000  x09 = 0000000000000000  x10 = 0000000000000000  x11 = 0000000000000000
x12 = 0000000000000000  x13 = 0000000000000000  x14 = 0000000000000000  x15 = 0000000000000000
x16 = 0000000000000000  x17 = 0000000000000000  x18 = 0000000000000000  x19 = 0000000000000000
x20 = 0000000000000000  x21 = 0000000000000000  x22 = 0000000000000000  x23 = 0000000000000000
x24 = 0000000000000000  x25 = 0000000000000000  x26 = 0000000000000000  x27 = 0000000000000000
x28 = 0000000000000000  x29 = 0000000000000000  x30 = 0000000000000000  x31 = 0000000000000000

64ビットに符号拡張されて計算された!上手く行ったようだ。

f:id:msyksphinz:20200822232459p:plain