TCG
动态翻译的基本思想就是把每一条Target指令切分成为若干条微指令,每条微指令由一段简单的C代码来实现,运行时通过一个动态代码生成器把这些微指令组合成一个函数,最后执行这个函数,就相当于执行了一条Target指令。那么现在的CPU指令这么多,怎么知道要分为哪些微指令呢?其实CPU指令看似名目繁多,异常复杂,实际上多数指令不外乎以下几大类:数据传送、算术运算、逻辑运算、程序控制;例如,数据传送包括:传送指令(如MOV)、堆栈操作(PUSH、POP)等,程序控制包括:函数调用(CALL)、转移指令(JMP)等;
上述流程图是从 CPU 初始化到生成 TCG 中间代码的过程,部分代码与 Target 强相关,从如下 References 可以看出
0x23 指令翻译核心函数 cpu_gen_code
变量 s 作为 TCG 上下文
TCGContext 的定义(部分代码)
struct TCGContext {
uint8_t *pool_cur, *pool_end;
TCGPool *pool_first, *pool_current;
TCGLabel *labels;
int nb_labels;
TCGTemp *temps; /* globals first, temps after */
int nb_globals;
int nb_temps;
/* index of free temps, -1 if none */
int first_free_temp[TCG_TYPE_COUNT * 2];
/* goto_tb support */
uint8_t *code_buf;
unsigned long *tb_next;
uint16_t *tb_next_offset;
uint16_t *tb_jmp_offset; /* != NULL if USE_DIRECT_JUMP */
/* liveness analysis */
uint16_t *op_dead_args; /* for each operation, each bit tells if the
corresponding argument is dead */
/* tells in which temporary a given register is. It does not take
into account fixed registers */
int reg_to_temp[TCG_TARGET_NB_REGS];
TCGRegSet reserved_regs;
tcg_target_long current_frame_offset;
tcg_target_long frame_start;
tcg_target_long frame_end;
int frame_reg;
uint8_t *code_ptr;
TCGTemp static_temps[TCG_MAX_TEMPS];
TCGHelperInfo *helpers;
int nb_helpers;
int allocated_helpers;
int helpers_sorted;
};
在对一个翻译块进行二进制翻译时,TCG 需要维护一些信息完成动态翻译,即 TCGContext,TCG 上下文包含三类信息:内存池、标号和变量1。
- TCGPool:内存池,用于二进制转换期间的内存管理。为了提高性能,TCG 在初始化期间分配了较大的存储空间(32K)。TCG 需要内存时,直接从中获取,当内存池剩余空间不满足申请需求,再申请新内存。内存池之间通过链表维护。
- TCG 变量:临时、局部和全局变量。在 TCG 上下文中,所有变量存放在一个静态分配的数组中。不再使用的变量,使用链表链接。先从被释放链表中分配,后从静态数组中分配。
目标指令翻译成 TCG 中间码
gen_intermediate_code() 函数定义在不同架构的 target-xxx/translate.c 文件中。该函数将 target 指令翻译成中间码。操作码和操作数分开存储。操作码存放在 gen_opc_buf变量,操作数存放在 gen_opparam_buf变量。翻译过程就是不断向上述两个缓冲区填充操作码和操作数。
将基本块(basic block, tb) 翻译成 TCG 中间代码(intermediate code)。
TCG 中间码翻译成宿主机指令
tcg_gen_code() 函数定义在 tcg.c 文件中。该函数将中间代码翻译成 host code。
将 TCG 中间码翻译成 host 机器码的函数是 tcg_gen_code_common 。主要过程如下
- 从缓存中找到 TCG 操作码和相应参数;
- 为输入参数分配 host 平台的寄存器