QEMUの構造について勉強したので、似たようなものをRustで作ってみたくなった。Rustの勉強も兼ねている。ターゲットについてはゲストコードをRISC-V、ホストコードをx86としてとりあえず何か動くものを作ってみたい。
RISC-Vの機械語は、例えば以下のようなものを考えてみる。
0x13, 0x05, 0xa0, 0x00, // addi a0,zero,10 0x67, 0x80, 0x00, 0x00, // ret => JALR x0, x1, 0
a0レジスタに10を代入して、ret命令を実行するだけの命令だ。これをx86に落とし込むためには、どのような構造を作ればいいのか考えてみる。
- 即値10を用意する。
- レジスタA10が定義されているアドレスに対して10をmoveする。
- 関数から戻る。x1に格納されている値にジャンプする。
まずはこのRISC-Vの機械語をデコードする。デコーダはすでに作ってあるのでこれをそのまま流用しよう。
for inst in &self.m_inst_vec { let riscv_id = match decode_inst(*inst) { Some(id) => id, _ => panic!("Decode Failed"), }; ...
正常にデコードされた場合は、TCG(Tiny Code Generator)に中間コードに変換する。
let tcg_inst = match riscv_id { RiscvInstId::ADDI => Self::tcg_gen_addi(inst), RiscvInstId::SUB => Self::tcg_gen_sub(inst), RiscvInstId::JALR => Self::tcg_gen_jalr(inst), _ => panic!("Not supported these instructions."), };
例えばADDI
にデコードされた場合にはtcg_gen_addi()
を呼び出すが、中身はTCGの構造体を生成するコードになっている。
fn tcg_gen_addi(inst: &u32) -> Box<TCGOp> { let rs1_addr: usize = get_rs1_addr!(*inst) as usize; let imm_const: u64 = (*inst as u64) >> 20 & 0xfff; let rd_addr: usize = get_rd_addr!(*inst) as usize; let rs1 = Box::new(TCGv::new_reg(rs1_addr as u64)); let imm = Box::new(TCGv::new_imm(imm_const)); let rd = Box::new(TCGv::new_reg(rd_addr as u64)); let tcg_inst = Box::new(TCGOp::new(TCGOpcode::ADD, *rd, *rs1, *imm)); tcg_inst }
これを蓄えていく仕組みになっている。TCGOp
構造体の構成は以下のようになっており、
#[derive(Debug, Copy, Clone)] struct TCGOp { op: TCGOpcode, arg0: TCGv, arg1: TCGv, arg2: TCGv, }
TCGv
は以下のような構成になっており、即値かレジスタのどちらかを取る。レジスタの場合は、CPUに定義されているレジスタへのポインタを示そうと思ったのだが、どうもlifetimeの関係上実装がとても難しくなったのであきらめ、レジスタアドレスのみを格納しておいた。
#[derive(Debug, Copy, Clone, PartialEq)] enum TCGvType { Register = 0, Immediate = 1, } #[derive(Debug, Copy, Clone)] struct TCGv { t: TCGvType, value: u64, }
arg0
には書き込み先レジスタのポインタ、arg1
には読み込みレジスタのポインタ、arg2
には即値が入る想定で実装している。
これで上記のRISC-V機械語をデコードすると以下のようになった。
inst = 00a00513 inst = 00008067 tcg_inst = TCGOp { op: ADD, arg0: TCGv { t: Register, value: 10 }, arg1: TCGv { t: Register, value: 0 }, arg2: TCGv { t: Immediate, value: 10 } } tcg_inst = TCGOp { op: JMP, arg0: TCGv { t: Register, value: 0 }, arg1: TCGv { t: Register, value: 1 }, arg2: TCGv { t: Immediate, value: 0 } }
TCGOp
: ADDの場合、書き込みレジスタはx10
つまりa10、読み込みレジスタはx0, 即値は10となっている。JMPの場合、書き込みレジスタはx0, 読み込みレジスタはx1、第3引数はなにも用意されていない。