QEMU実装の続き。命令実装を進めていく中で、次に実装すべきは分岐命令だ。QEMUで分岐命令をどのように実装するかについて検討を行う。
まずは分岐命令の定義だ。分岐命令はデコードテーブルの中で以下のようにして定義する。
qemu/target/myriscvx/insn32.decode
# Branch Instructions beq ....... ..... ..... 000 ..... 1100011 @b bne ....... ..... ..... 001 ..... 1100011 @b blt ....... ..... ..... 100 ..... 1100011 @b bge ....... ..... ..... 101 ..... 1100011 @b bltu ....... ..... ..... 110 ..... 1100011 @b bgeu ....... ..... ..... 111 ..... 1100011 @b
次にTranslatorの定義を行う。Translatorで実施すべきことは、
- 2つのレジスタを読み込む。
- 比較を行う。比較が成立したら次の場所までジャンプする命令を実行する。
- 比較が成立しなかった場合は、PCを進めるだけで何もしない。
となる。これを単純に実装すると以下のようになる。
qemu/target/myriscvx/insn_trans/trans_rvi.inc.c
static bool trans_beq(DisasContext *ctx, arg_beq *a) { TCGLabel *label = gen_new_label(); TCGv rs1_val = tcg_temp_new(); TCGv rs2_val = tcg_temp_new(); gen_get_gpr(rs1_val, a->rs1); gen_get_gpr(rs2_val, a->rs2); TCGv res = tcg_temp_new(); tcg_gen_brcond_tl(TCG_COND_EQ, rs1_val, rs2_val, label); gen_goto_tb(ctx, 0, ctx->base.pc_next + 4); // False Condition: Go through gen_set_label(label); // True Condition --> Jump gen_goto_tb(ctx, 0, ctx->base.pc_next + a->imm); tcg_temp_free(rs1_val); tcg_temp_free(rs2_val); tcg_temp_free(res); return true; }
- 2つのレジスタ値を取り出す。これは
gen_get_gpr()
により実現される。 - 比較は
tcg_gen_brcond_tl()
により実行する。今回はBEQ命令なのでTCG_COND_EQ
オペレーションが適用される。もしも比較が成立した場合、ラベルlabel
に飛ぶような命令が生成される。 label
に飛ばない場合は直後のgen_goto_tb()
が実行される。これはこのTB自体を終了させる意味合いを持っており、次のPC+4に命令実行を移す。label
を配置し、以下に分岐成立時の処理を記述する。ここではgen_goto_tb
によりジャンプ先にジャンプする記述が実装されている。
というわけで実装内でラベルを配置してまるでさながらアセンブリ命令のように処理を配置していくわけだ。これでBEQ命令を実行できる。
で、これをBEQ命令以外のBNE, BLT, BGE, BLTU, BGEU
も同じように実装するわけだが、面倒なのでdefine
マクロを作って統一化した。
#define TCG_BRANCH(op) \ TCGLabel *label = gen_new_label(); \ \ TCGv rs1_val = tcg_temp_new(); \ TCGv rs2_val = tcg_temp_new(); \ \ gen_get_gpr(rs1_val, a->rs1); \ gen_get_gpr(rs2_val, a->rs2); \ \ TCGv res = tcg_temp_new(); \ tcg_gen_brcond_tl(op, rs1_val, rs2_val, label); \ gen_goto_tb(ctx, 1, ctx->pc_succ_insn); /* False Condition: Go through */ \ gen_set_label(label); /* True Condition --> Jump */ \ gen_goto_tb(ctx, 0, ctx->base.pc_next + a->imm); \ \ tcg_temp_free(rs1_val); \ tcg_temp_free(rs2_val); \ tcg_temp_free(res); \ \ return true;
すると各分岐命令の実装は以下のようになる。
static bool trans_bne(DisasContext *ctx, arg_bne *a) { TCG_BRANCH(TCG_COND_NE); } static bool trans_blt(DisasContext *ctx, arg_blt *a) { TCG_BRANCH(TCG_COND_LT); } static bool trans_bge(DisasContext *ctx, arg_bge *a) { TCG_BRANCH(TCG_COND_GE); } static bool trans_bltu(DisasContext *ctx, arg_bltu *a) { TCG_BRANCH(TCG_COND_LTU); } static bool trans_bgeu(DisasContext *ctx, arg_bgeu *a) { TCG_BRANCH(TCG_COND_GEU); }
これでとりあえず完了だ。ここまでの実装を来ないQEMUをリコンパイルして実行すると、以下のようなトレースが出力された。
$ qemu-system-myriscvx64 --machine virt --d in_asm \ --nographic --trace enable=myriscvx_trap --kernel rv64ui-p-simple
Priv: 3; Virt: 0 0x0000000000001000: 00000297 auipc t0,0 # 0x1000 0x0000000000001004: 02028593 addi a1,t0,32 0x0000000000001008: f1402573 csrrs a0,mhartid,zero ---------------- IN: Priv: 3; Virt: 0 0x000000000000100c: 0182b283 ld t0,24(t0) 0x0000000000001010: 00028067 jr t0 ---------------- IN: Priv: 3; Virt: 0 0x0000000080000000: 04c0006f j 76 # 0x8000004c ---------------- IN: Priv: 3; Virt: 0 0x000000008000004c: f1402573 csrrs a0,mhartid,zero ---------------- IN: Priv: 3; Virt: 0 0x0000000080000050: 00051063 bnez a0,0 # 0x80000050 0x0000000080000054: 00000297 auipc t0,0 # 0x80000054 0x0000000080000058: 01028293 addi t0,t0,16 0x000000008000005c: 30529073 csrrw zero,mtvec,t0 ---------------- IN: Priv: 3; Virt: 0 0x0000000080000054: 00000297 auipc t0,0 # 0x80000054 0x0000000080000058: 01028293 addi t0,t0,16 0x000000008000005c: 30529073 csrrw zero,mtvec,t0 30487@1595649721.398664:myriscvx_trap hart:0, async:0, cause:2, epc:0x8000005c, tval:0x0, desc=illegal_instruction ----------------
分岐命令が実行されることを確認できた。しかし今度はcsrrw
で再び命令例外だ。これはmtvec
レジスタを実装していないことを意味する。先は長いなあ。。。