TCGの続き。TCGによるエンコードをもっと詳しく見るために、TCGContext
を眺めてみる。
qemu/include/tcg/tcg.h
struct TCGContext { uint8_t *pool_cur, *pool_end; TCGPool *pool_first, *pool_current, *pool_first_large; int nb_labels; ... TCGTemp *frame_temp; tcg_insn_unit *code_ptr; #ifdef CONFIG_PROFILER TCGProfile prof; #endif ... };
このTCGContext
のcode_ptr
に、命令コードが挿入されている気がする。例えば、ADDI
命令のTCGを作るとするならば、
qemu/tcg/riscv/tcg-target.inc.c
static void tcg_out_addsub2(TCGContext *s, TCGReg rl, TCGReg rh, TCGReg al, TCGReg ah, TCGArg bl, TCGArg bh, bool cbl, bool cbh, bool is_sub, bool is32bit) { ... } else if (bh != 0 || ah == rl) { tcg_out_opc_imm(s, opc_addi, th, ah, (is_sub ? -bh : bh)); } else {
tcg_out_opt_imm()
がどのような定義になっているのか。
static void tcg_out_opc_imm(TCGContext *s, RISCVInsn opc, TCGReg rd, TCGReg rs1, TCGArg imm) { tcg_out32(s, encode_i(opc, rd, rs1, imm)); }
#if TCG_TARGET_INSN_UNIT_SIZE <= 4 static __attribute__((unused)) inline void tcg_out32(TCGContext *s, uint32_t v) { if (TCG_TARGET_INSN_UNIT_SIZE == 4) { *s->code_ptr++ = v; } else { tcg_insn_unit *p = s->code_ptr; memcpy(p, &v, sizeof(v)); s->code_ptr = p + (4 / TCG_TARGET_INSN_UNIT_SIZE); } }
TCGContext
のcode_ptr
にエンコードされた機械語を挿入していくようだ。このTCGContext
はグローバル変数として定義されているような、気がする。
qemu/accel/tcg/translate-all.c
/* code generation context */ TCGContext tcg_init_ctx; __thread TCGContext *tcg_ctx; TBContext tb_ctx; bool parallel_cpus;
TCGContext
にコードが格納されるまでを追いかけると、
cpu_exec()
により命令実行が開始される。
/* main execution loop */ int cpu_exec(CPUState *cpu) { ...
cpu_exec()
の中では、すでにTCGに変換したコードかどうかを探すルーチンが存在している。
... tb = tb_find(cpu, last_tb, tb_exit, cflags); cpu_loop_exec_tb(cpu, tb, &last_tb, &tb_exit); /* Try to align the host and virtual clocks ...
tb_find()
で探索に失敗すると、その場でTCGの変換が始まる。
static inline TranslationBlock *tb_find(CPUState *cpu, TranslationBlock *last_tb, int tb_exit, uint32_t cf_mask) { ... tb = tb_lookup__cpu_state(cpu, &pc, &cs_base, &flags, cf_mask); if (tb == NULL) { mmap_lock(); tb = tb_gen_code(cpu, pc, cs_base, flags, cf_mask); mmap_unlock(); /* We add the TB in the virtual pc hash table for the fast lookup */ atomic_set(&cpu->tb_jmp_cache[tb_jmp_cache_hash_func(pc)], tb); }
tb_gen_code()
では命令の変換が始まる。tcg_gen_code()
がメインの変換ルーチンだ。
qemu/accel/tcg/translate-all.c
/* Called with mmap_lock held for user mode emulation. */ TranslationBlock *tb_gen_code(CPUState *cpu, target_ulong pc, target_ulong cs_base, uint32_t flags, int cflags) { CPUArchState *env = cpu->env_ptr; TranslationBlock *tb, *existing_tb; ... gen_code_size = tcg_gen_code(tcg_ctx, tb); if (unlikely(gen_code_size < 0)) { switch (gen_code_size) { case -1: /*
tcg_gen_code()
ではTCGの中間コードの種類に応じて生成するTCGを変換する。
qemu/tcg/tcg.c
int tcg_gen_code(TCGContext *s, TranslationBlock *tb) { #ifdef CONFIG_PROFILER TCGProfile *prof = &s->prof; #endif int i, num_insns; ... QTAILQ_FOREACH(op, &s->ops, link) { TCGOpcode opc = op->opc; #ifdef CONFIG_PROFILER atomic_set(&prof->table_op_count[opc], prof->table_op_count[opc] + 1); #endif switch (opc) { case INDEX_op_mov_i32: case INDEX_op_mov_i64: case INDEX_op_mov_vec: ... break; default: /* Sanity check that we've not introduced any unhandled opcodes. */ tcg_debug_assert(tcg_op_supported(opc)); /* Note: in order to speed up the code, it would be much faster to have specialized register allocator functions for some common argument patterns */ tcg_reg_alloc_op(s, op); break; ...
今回は単なる加算命令なので、tcg_reg_alloc_op()
命令の変換が行われる。
static void tcg_reg_alloc_op(TCGContext *s, const TCGOp *op) { const TCGLifeData arg_life = op->life; const TCGOpDef * const def = &tcg_op_defs[op->opc]; TCGRegSet i_allocated_regs; TCGRegSet o_allocated_regs; int i, k, nb_iargs, nb_oargs; TCGReg reg; ... /* emit instruction */ if (def->flags & TCG_OPF_VECTOR) { tcg_out_vec_op(s, op->opc, TCGOP_VECL(op), TCGOP_VECE(op), new_args, const_args); } else { tcg_out_op(s, op->opc, new_args, const_args); }
tcg_out_op()
が呼び出される。ここから先はターゲットアーキテクチャ毎に変換ルーチンが実装されている。
qemu/tcg/riscv/tcg-target.inc.c
static void tcg_out_op(TCGContext *s, TCGOpcode opc, const TCGArg *args, const int *const_args) { TCGArg a0 = args[0]; TCGArg a1 = args[1]; TCGArg a2 = args[2]; int c2 = const_args[2]; ... case INDEX_op_add_i64: if (c2) { tcg_out_opc_imm(s, OPC_ADDI, a0, a1, a2); } else { tcg_out_opc_reg(s, OPC_ADD, a0, a1, a2); } break;
ここまでで、tcg_out_opc_imm()
までやってきた。これまでの変換の流れを追うと以下のようになる。