FPGA開発日記

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

QEMUを作る(TCGからx86へのDynamic Translation)

RISC-Vの機械語から簡単なTCGが生成できるようになったので、次はx86のコードに変換する。基本的な考え方は、特定のTCGを特定の命令に変換していくのだが、例えばRISC-VのADDI x10, x11, 20などの命令をx86に置き換えるならば、

  • x11が定義されている場所(アドレス)から値をロード
  • 任意の場所に定数20を生成。
  • 加算を実行
  • x10が定義されている場所(アドレス)に加算結果をストア

となる。ここではまだ任意の場所を定義する機能を実装していないので中間レジスタを取ることができていないが、x86はメモリに直接値をストアすることができるのでまあそれを使用させてもらう。

ADDのTCGx86に変換する場合、以下のようにした。ただし実装は未完成で、即値を代入する場合のみ対応している。

    fn translate_addi(tcg: &TCGOp) -> (u64, usize) {
        assert_eq!(tcg.arg0.t, TCGvType::Register);
        assert_eq!(tcg.arg1.t, TCGvType::Register);
        assert_eq!(tcg.arg2.t, TCGvType::Immediate);

        if tcg.arg0.value == 0 {
            // if destination is x0, skip generate host machine code.
            return (0, 0);
        }
        if tcg.arg1.value == 0 {
            // if source register is x0, just generate immediate value.
            let raw_mc: u64 = 0x48c74508_00000000 | tcg.arg2.value;
            return (raw_mc, 64 / 8);
        }
        panic!("This code doesn't support now!");
    }

0x48c74508_00000000というのがmov命令に相当し、下位の4バイトに即値の設定をして即値を生成する。こうしてx86への変換を行う。

ret命令についても同様。x86ret命令に変換する。ただしこれは実際にはプログラムカウンタに値を設定するだけなので、ret命令に変換するというよりもmovに変換した方が簡単そうな気がする。

    fn translate_jmp(tcg: &TCGOp) -> (u64, usize) {
        assert_eq!(tcg.op, TCGOpcode::JMP);
        if tcg.arg0.t == TCGvType::Register
            && tcg.arg0.value == 0
            && tcg.arg1.t == TCGvType::Register
            && tcg.arg1.value == 1
        {
            let raw_mc: u64 = 0xc3;
            return (raw_mc, 1);
        }
        panic!("This function is not supported!")
    }

という訳で、以下のRISC-V機械語を変換してx86命令を出力すると以下のようになった。

0x13, 0x05, 0xa0, 0x00, // addi a0,zero,10
0x67, 0x80, 0x00, 0x00, // ret
48 c7 45 08 00 00 00 0a  // mov命令
c3                      // ret命令

でもこれは良くない。実際にはRust実装内で定義したレジスタの位置を正しく指せていないからだ。x86のメインルーチンを実行する前に、もう少しお膳立てのコードを生成してポインタの位置を調整する必要がありそうだ。