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ビットに符号拡張されて計算された!上手く行ったようだ。