RISC-Vの機械語から簡単なTCGが生成できるようになったので、次はx86のコードに変換する。基本的な考え方は、特定のTCGを特定の命令に変換していくのだが、例えばRISC-VのADDI x10, x11, 20
などの命令をx86に置き換えるならば、
x11
が定義されている場所(アドレス)から値をロード- 任意の場所に定数20を生成。
- 加算を実行
x10
が定義されている場所(アドレス)に加算結果をストア
となる。ここではまだ任意の場所を定義する機能を実装していないので中間レジスタを取ることができていないが、x86はメモリに直接値をストアすることができるのでまあそれを使用させてもらう。
ADDのTCGをx86に変換する場合、以下のようにした。ただし実装は未完成で、即値を代入する場合のみ対応している。
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
命令についても同様。x86のret
命令に変換する。ただしこれは実際にはプログラムカウンタに値を設定するだけなので、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のメインルーチンを実行する前に、もう少しお膳立てのコードを生成してポインタの位置を調整する必要がありそうだ。