Binary Translation型エミュレータを作っている。ゲストマシンはRISC-Vで、ホストマシンはx86である。ADDI命令は簡単なものであれば実装できるようになった(ソースレジスタがx0の場合に限る)。どんどん実装を進めていこう。次は普通のADDI命令と、ADD命令を実装することを考える。
という構成になっている。これらの命令を受け取ると、TCGは簡単に書き下すと以下のように構成される。
ADDI rd,rs1,imm
TCG(opcode::ADD, TCGvType::Register rd, TCGvType::Register rs1, TCGvType::Immediate imm)
ADD rd,rs1,rs2
TCG(opcode::ADD, TCGvType::Register rd, TCGvType::Register rs1, TCGvType::Register rs2)
共通フォーマットに変換されれば、次はこれをx86命令に変換する。基本的なポリシは以下の通りだ。
ADDI
命令- 即値
imm
と%eax
レジスタを加算して、結果を汎用レジスタに格納する(例えば%eax
)。 ADD
命令- 汎用レジスタ
rs2
がインスタンスされているアドレスから値をロードして%eax
レジスタを加算し、結果を汎用レジスタに格納する(例えば%eax
)。 - 汎用レジスタ(例えば
%eax
)に格納されている値を、汎用レジスタrd
がインスタンスされているアドレスにストアする。
となる。
まずはADDI
命令からだ。これはtcg_gen_rri()
で実現する。
fn tcg_gen_rri(op: X86Opcode, tcg: &tcg::TCGOp, mc: &mut Vec<u8>) { assert_eq!(tcg.arg0.t, TCGvType::Register); assert_eq!(tcg.arg1.t, TCGvType::Register); assert_eq!(tcg.arg2.t, TCGvType::Immediate); // mov reg_offset(%rbp),%eax Self::tcg_modrm_out(X86Opcode::MOV_GV_EV, X86ModRM::MOD_10, mc); Self::tcg_out(conv_gpr_offset!(tcg.arg1.value), 4, mc); // add imm16,%eax Self::tcg_out(op as u32, 1, mc); Self::tcg_out(tcg.arg2.value as u32, 4, mc); // mov %eax,reg_offset(%rbp) Self::tcg_modrm_out(X86Opcode::MOV_EV_GV, X86ModRM::MOD_10, mc); Self::tcg_out(conv_gpr_offset!(tcg.arg0.value), 4, mc); }
MOV_GV_EV
はEVで指定されるアドレスから値をロードしてGVで示される汎用レジスタに格納する命令だ。Mod/RMでこれらを指定する。
次に演算命令を実行する。これはop
で指定している。加算ならばADD_RAX_IV
を指定する。オペランドとしてTCGの第2引数の即値を指定する。これにより、%eax
と第2引数の加算が実行され、結果を%eax
に格納する。
MOV_EV_GV
は汎用レジスタに格納された値をEVで指定されるアドレスにストアする。Mod/RMでこれらを指定する。
次はADD
命令だ。これはtcg_gen_rrr()
で実現する。
fn tcg_gen_rrr(op: X86Opcode, tcg: &tcg::TCGOp, mc: &mut Vec<u8>) { assert_eq!(tcg.arg0.t, TCGvType::Register); assert_eq!(tcg.arg1.t, TCGvType::Register); assert_eq!(tcg.arg1.t, TCGvType::Register); // mov reg_offset(%rbp),%eax Self::tcg_modrm_out(X86Opcode::MOV_GV_EV, X86ModRM::MOD_10, mc); Self::tcg_out(conv_gpr_offset!(tcg.arg1.value), 4, mc); // add reg_offset(%rbp),%eax Self::tcg_modrm_out(op, X86ModRM::MOD_10, mc); Self::tcg_out(conv_gpr_offset!(tcg.arg2.value), 4, mc); // mov %eax,reg_offset(%rbp) Self::tcg_modrm_out(X86Opcode::MOV_EV_GV, X86ModRM::MOD_10, mc); Self::tcg_out(conv_gpr_offset!(tcg.arg0.value), 4, mc); }
MOV_GV_EV
はEVで指定されるアドレスから値をロードしてGVで示される汎用レジスタに格納する命令だ。Mod/RMでこれらを指定する。
次に演算命令を実行する。これはop
で指定している。加算ならばADD_GV_EV
を指定する。オペランドとしてTCGの第2引数を指定する。これにより、%eax
と第2引数の加算が実行され、結果を%eax
に格納する。
MOV_EV_GV
は汎用レジスタに格納された値をEVで指定されるアドレスにストアする。Mod/RMでこれらを指定する。
この方針でOKだ。テストパタンを実行してみよう。
simple_start2.S
_start: addi x1, zero, 1 addi x2, x1, 2 addi x3, x2, 3 ... addi x17, x16, 17 addi x18, x17, 18 addi x19, x18, 19 addi x20, x19, 20 add x21, x20, x19 add x22, x21, x20 ... add x30, x29, x28 add x31, x30, x29 sub x31, x30, x29 ret
$ cargo run /home/msyksphinz/work/riscv/qemu/qemu_test/simple_start2
x00 = 0000000000000000 x01 = 0000000000000001 x02 = 0000000000000003 x03 = 0000000000000006 x04 = 000000000000000a x05 = 000000000000000f x06 = 0000000000000015 x07 = 000000000000001c x08 = 0000000000000024 x09 = 000000000000002d x10 = 0000000000000037 x11 = 0000000000000042 x12 = 000000000000004e x13 = 000000000000005b x14 = 0000000000000069 x15 = 0000000000000078 x16 = 0000000000000088 x17 = 0000000000000099 x18 = 00000000000000ab x19 = 00000000000000be x20 = 00000000000000d2 x21 = 0000000000000190 x22 = 0000000000000262 x23 = 00000000000003f2 x24 = 0000000000000654 x25 = 0000000000000a46 x26 = 000000000000109a x27 = 0000000000001ae0 x28 = 0000000000002b7a x29 = 000000000000465a x30 = 00000000000071d4 x31 = 0000000000002b7a
命令実行後のレジスタ状態も問題なさそうだ。