翻译 qemu tcg 介绍
1.介绍
TCG(微型代码生成器)最初是C编译器的通用后端。 它被简化为可以在QEMU中使用。 它也起源于Paul Brook编写的QOP代码生成器。
2.定义
TCG吸收类似于RISC的“ TCG ops”定义,并对它们执行一些优化,包括活动性分析(liveness analysis)和琐碎的常量表达式评估(trivial constant expression evaluation)。然后,在宿主CPU后端(也称为TCG“target”)中实现TCG操作。
TCG “target”:是我们为其生成代码的体系结构。当然,它与QEMU的“target”(即模拟体系结构)不同。当TCG开始作为用于交叉编译的通用C后端时,尽管QEMU从来没有这样的假设,但TCG目标不同于主机。
在本文档中,我们使用“ guest”来指定我们正在仿真的体系结构。 “target”始终表示TCG目标,即运行QEMU的计算机。
TCG“function”:对应于QEMU转换块(TB)。
TCG“temporary”:是仅存在于基本块中的变量。在每个函数中显式分配了临时项。
TCG“local temporary”:是仅存在于函数中的变量。在每个函数中显式分配了本地临时变量。
TCG“global”:是所有函数中都存在的变量(等效于C全局变量)。它们在定义的功能之前定义。 TCG全局可以是内存地址(例如QEMU CPU寄存器),固定宿主机器寄存器(例如QEMU CPU状态指针)或存储在QEMU TB之外的寄存器中的内存地址(尚未实现)。
TCG“basic block”对应于由分支指令终止的指令列表。
具有“unspecified behavior”:的操作可能会导致崩溃。
具有“unspecified behavior”的操作不应崩溃。但是,结果可能是几种可能性之一,因此可以认为是“不确定的结果”。
3.中间表示
3.1 介绍
TCG指令对临时变量,局部临时变量或全局变量进行操作。 TCG指令和变量是强类型的。 支持两种类型:32位整数和64位整数。 根据TCG目标字的大小,指针被定义为32位或64位整数的别名。
每个指令都有固定数量的输出变量操作数、输入变量操作数、始终为常数的操作数。
值得注意的例外是具有可变数量的输出和输入的调用指令。
以文本形式,输出操作数通常是第一个,然后是输入操作数,然后是常量操作数。 输出类型包含在指令名称中。 常量以“ $”为前缀。
add_i32 t0,t1,t2(表示t0 <-t1 + t2)
3.2 假设
基本块
- 基本块在分支(例如brcond_i32指令),goto_tb和exit_tb指令之后结束。
- 基本块在上一个基本块结束后或在set_label指令后开始。
基本块结束后,临时对象的内容将被破坏,但本地临时对象和全局变量将被保留。
尚不支持浮点类型
指针:取决于TCG目标,指针大小为32位或64位。 TCG_TYPE_PTR类型是TCG_TYPE_I32或TCG_TYPE_I64的别名。
帮助函数:
使用tcg_gen_helper_x_y
可以调用任何采用i32,i64或指针类型的函数。默认情况下,在调用帮助函数前,所有全局变量都存储在其规范位置,并且假定该函数可以对其进行修改。默认情况下,允许帮助函数修改CPU状态或引发异常。
可以使用以下功能修饰符来覆盖它:
TCG_CALL_NO_READ_GLOBALS
表示帮助程序不会直接或通过异常读取全局变量。在调用帮助函数之前,不会将它们保存到规范位置。TCG_CALL_NO_WRITE_GLOBALS
表示帮助程序不会修改任何全局变量。它们只会在调用帮助函数之前保存到规范位置,但之后将不会重新加载。TCG_CALL_NO_SIDE_EFFECTS
表示如果不使用返回值,则对该函数的调用将被删除。
请注意,TCG_CALL_NO_READ_GLOBALS
意味着TCG_CALL_NO_WRITE_GLOBALS
。
在某些TCG目标(例如x86)上,支持多种调用约定。
分支:
- 使用指令
br
跳转到标签。
3.3 代码优化
生成说明时,至少可以依靠以下优化:
简化了单个指令,例如下面代码将被压缩
and_i32 t0,t0,$ 0xffffffff
在基本块级别进行存活分析。 该信息用于禁止从死变量移动到另一个变量。 它还用于删除计算无效结果的指令。 后者对于QEMU中的条件代码优化特别有用。
在以下示例中:
1 | add_i32 t0,t1,t2 |
仅保留最后一条指令。
3.4 指令参考
3.4.1 函数调用
call <ret> <params> ptr
调用函数 ‘ptr’ (指针类型)
<ret>
可选地 32 bit or 64 bit 返回值<params>
可选地 32 bit or 64 bit 参数
3.4.2 Jumps/Labels
set_label $label
在当前程序点定义标签’label’
br $label
跳转到标签
brcond_i32/i64 t0, t1, cond, label
当t0与t1的条件运算cond的结果为true时跳转到label
条件运算cond可以是:
1 | TCG_COND_EQ |
3.4.3 数学运算
add_i32/i64 t0, t1, t2
t0=t1+t2
sub_i32/i64 t0, t1, t2
t0=t1-t2
neg_i32/i64 t0, t1
t0=-t1 (补码形式)
mul_i32/i64 t0, t1, t2
t0=t1*t2
div_i32/i64 t0, t1, t2
t0=t1/t2 (有符号). 如果被零除或溢出,则行为不确定。
divu_i32/i64 t0, t1, t2
t0=t1/t2 (无符号). 如果被零除,则行为不确定。
rem_i32/i64 t0, t1, t2
t0=t1%t2 (有符号). 如果被零除或溢出,则行为不确定。
remu_i32/i64 t0, t1, t2
t0=t1%t2 (无符号). 如果被零除,则行为不确定。
3.4.4 逻辑运算
and_i32/i64 t0, t1, t2
t0=t1&t2
or_i32/i64 t0, t1, t2
t0=t1|t2
xor_i32/i64 t0, t1, t2
t0=t1^t2
not_i32/i64 t0, t1
t0=~t1
andc_i32/i64 t0, t1, t2
t0=t1&~t2
eqv_i32/i64 t0, t1, t2
t0=(t1^t2), or equivalently, t0=t1^t2
nand_i32/i64 t0, t1, t2
t0=~(t1&t2)
nor_i32/i64 t0, t1, t2
t0=~(t1|t2)
orc_i32/i64 t0, t1, t2
t0=t1|~t2
clz_i32/i64 t0, t1, t2
t0 = t1 ? clz(t1) : t2
ctz_i32/i64 t0, t1, t2
t0 = t1 ? ctz(t1) : t2
3.4.5 移位/旋转
shl_i32/i64 t0, t1, t2
t0=t1 << t2. Unspecified behavior if t2 < 0 or t2 >= 32 (resp 64)
shr_i32/i64 t0, t1, t2
t0=t1 >> t2 (unsigned). Unspecified behavior if t2 < 0 or t2 >= 32 (resp 64)
sar_i32/i64 t0, t1, t2
t0=t1 >> t2 (signed). Unspecified behavior if t2 < 0 or t2 >= 32 (resp 64)
rotl_i32/i64 t0, t1, t2
t1向左旋转t2位
Unspecified behavior if t2 < 0 or t2 >= 32 (resp 64)
rotr_i32/i64 t0, t1, t2
t1向右旋转t2位
Unspecified behavior if t2 < 0 or t2 >= 32 (resp 64)
3.4.6 杂项
mov_i32/i64 t0, t1
t0 = t1
t1移动到t0 (两个操作符类型必须相同).
1 | ext8s_i32/i64 t0, t1 |
8, 16 or 32 bit sign/zero 扩展 (both operands must have the same type)
bswap16_i32/i64 t0, t1
16 bit byte swap on a 32/64 bit value. It assumes that the two/six high order
bytes are set to zero.
bswap32_i32/i64 t0, t1
32 bit byte swap on a 32/64 bit value. With a 64 bit value, it assumes that
the four high order bytes are set to zero.
bswap64_i64 t0, t1
64 bit byte swap
discard_i32/i64 t0
指示t0的值之后不再使用,这对强制删除无效代码很有效。
deposit_i32/i64 dest, t1, t2, pos, len
将T2作为位域存入T1,并将结果放入DEST。 位字段由POS / LEN描述,它们是立即值:
LEN - 位域长度
POS - 从LSB开始计算的第一位的位置
例如, deposit_i32 dest, t1, t2, 8, 4
表示 从bit8开始的4-bit位域. 此操作等效于
dest = (t1 & ~0x0f00) | ((t2 << 8) & 0x0f00)
1 | extract_i32/i64 dest, t1, pos, len |
从T1中提取一个位域,并将结果放入DEST。 位字段由POS / LEN描述,它们是即时值,如上用于存款。 对于extract_ *,结果将用零扩展到左侧;否则,结果将扩展为0。 对于sextract_ *,结果将扩展到左侧,并带有pos + len-1的位域符号位的副本。
例如”sextract_i32 dest, t1, 8, 4” 表示从bit8开始的一段4-bit位域. 操作等效于
dest = (t1 << 20) >> 28
.
extract2_i32/i64 dest, t1, t2, pos
对于N = {32,64},从t2:t1的串联中提取一个N位数,从pos开始。 tcg_gen_extract2_ {i32,i64}扩展器接受0 <= pos <= N作为输入。 后端代码生成器将看不到0或N作为这些操作码的输入。
extrl_i64_i32 t0, t1
仅对于64位主机,提取输入T1的低32位并将其放入32位输出T0。 根据主机,这可能是一个简单的举动,或者可能需要其他规范化。
extrh_i64_i32 t0, t1
仅对于64位主机,提取输入T1的高32位并将其放入32位输出T0。 根据主机的不同,这可能是简单的转换,或者可能需要其他规范化。
3.4.7 条件移动
setcond_i32/i64 dest, t1, t2, cond
dest = (t1 cond t2)
当t1与t2在条件运算cond的结果为True则设置dest为1,否则为0
movcond_i32/i64 dest, c1, c2, v1, v2, cond
dest = (c1 cond c2 ? v1 : v2)
当t1与t2在条件运算cond的结果为True则设置dest为V1,否则为V2
3.4.8 类型转换
ext_i32_i64 t0, t1
将t1(32位)转换为t0(64位)并进行符号扩展
extu_i32_i64 t0, t1
将t1(32位)转换为t0(64位)并进行0扩展
trunc_i64_i32 t0, t1
将t1(64位)截断为t0(32位)
concat_i32_i64 t0, t1, t2
构造t0(64位),取t1(32位)的低半部分和t2(32位)的高半部分。
concat32_i64 t0, t1, t2
构造t0(64位),取t1(64位)的低半部分和t2(64位)的高半部分。
3.4.9 加载/存放
1 | ld_i32/i64 t0, t1, offset |
t0 = read(t1 + offset)
从宿主机器内存加载8位,16位,32位或64位带或不带符号扩展名。 偏移量必须为常数。
1 | st_i32/i64 t0, t1, offset |
write(t0, t1 + offset)
将8、16、32或64位写入宿主机器内存。
所有这些操作码均假定指向的主机内存与全局内存不对应。 在后一种情况下,行为是不可预测的。
3.4.10 多字(multiword)算术
1 | add2_i32/i64 t0_low, t0_high, t1_low, t1_high, t2_low, t2_high |
与add / sub相似,不同之处在于双字输入T1和T2由两个单字参数组成,并且双字输出T0在两个单字输出中返回。
mulu2_i32/i64 t0_low, t0_high, t1, t2
与mul相似,除了两个无符号输入T1和T2产生完整的双字乘积T0。 后者以两个单字输出返回。
muls2_i32/i64 t0_low, t0_high, t1, t2
与mulu2相似,除了两个输入T1和T2是带符号的。
1 | mulsh_i32/i64 t0, t1, t2 |
分别提供有符号或无符号乘法的高部分。 如果后端未提供mulu2 / muls2,则tcg-op生成器可以获得相同的结果,方法是发出一对操作码mul + muluh / mulsh。
3.4.11 内存屏障
mb <$ arg>
生成target内存屏障指令,以确保由相应的Host内存屏障指令强制执行的内存排序。 后端执行的排序可能比guest要求的排序严格。 它不能更弱。 该操作码采用一个常量参数,该参数是生成适当的屏障指令所必需的。 后端应注意仅在必要时(即,对于SMP来宾和启用MTTCG时)发出目标屏障指令。
guest翻译器应为所有具有顺序副作用的guest指令生成此操作码。
请参阅docs / devel / atomics.txt了解有关内存屏障的更多信息。
3.4.12 32位宿主机器模拟64位客户机器
以下操作码是TCG内部的。 因此,它们将由32位主机代码生成器实现,而不由来宾转换器发出。 它们由“ tcg-op.h”中的内联函数根据需要发出。
brcond2_i32 t0_low,t0_high,t1_low,t1_high,cond,label
与brcond相似,不同之处在于64位值T0和T1由两个32位自变量形成。
setcond2_i32 dest,t1_low,t1_high,t2_low,t2_high,cond
与setcond相似,除了64位值T1和T2由两个32位自变量形成。 结果是一个32位值。
3.4.13 QEMU特定指令
exit_tb t0
退出当前TB,并返回值t0(字类型word type)。
goto_tb index
如果当前TB已链接到该TB,则退出当前TB并跳转到TB索引“index”(常量)。否则,执行下一条指令。只有索引0和1有效,并且每TB每个插槽索引最多只能发布一次tcg_gen_goto_tb。
lookup_and_goto_ptr tb_addr
查找TB地址(’tb_addr’),如果有效,则跳转到该地址。如果无效,请跳至TCG尾声以返回到exec循环。
此操作是可选的。如果TCG后端未实现goto_ptr操作码,则发出此操作等同于发出exit_tb(0)。
qemu_ld_i32 / i64 t0,t1,flag,memidx
qemu_st_i32 / i64 t0,t1,flag,memidx
将guest地址t1的数据加载到t0,或将数据存储在guest地址t1的t0中。 _i32 / _i64大小仅适用于输入/输出寄存器t0的大小。地址t1始终根据来宾的大小而定,并且存储器操作的宽度由标志控制。
如果处理32位主机上的64位数量,则t0和t1都可以分成低位序排序的寄存器对。
memidx选择要使用的qemu tlb索引(例如,用户或内核访问权限)。这些标志是MemOp位,用于选择内存访问的符号,宽度和字节序。
对于32位主机,保证qemu_ld / st_i64仅与标志中指定的64位内存访问。
宿主机器向量操作
所有向量操作都有两个参数:TCGOP_VECL
和TCGOP_VECE
。前者以log2 64位为单位指定向量的长度。后者以log2 8位为单位指定元素的长度(如果适用)。
例如: VECL = 1-> 64 << 1-> v128
,而VECE = 2-> 1 << 2-> i32
。
1 | mov_vec v0,v1 |
移动,加载和存储。
dup_vec v0,r1
通过V0将R1的低N位复制到VECL / VECE副本中。
dupi_vec v0,c
同样,对于常量。
较小的值将由扩展器复制到主机寄存器的大小。
dup2_vec v0,r1,r2
跨V0将r2:r1复制到VECL / 64副本中。该操作码仅适用于32位主机。
add_vec v0,v1,v2
v0 = v1 + v2,在向量中的元素中。
sub_vec v0,v1,v2
同样,v0 = v1-v2。
mul_vec v0,v1,v2
同样,v0 = v1 * v2。
neg_vec v0,v1
同样,v0 = -v1。
abs_vec v0,v1
同样,v0 = v1 <0? -v1:v1,位于向量中的元素中。
smin_vec:
umin_vec:
同样,对于有符号和无符号元素类型,v0 = MIN(v1,v2)。
smax_vec:
umax_vec:
同样,对于有符号和无符号元素类型,v0 = MAX(v1,v2)。
1 | ssadd_vec: |
有符号和无符号饱和加法和减法。如果在元素类型内无法表示真实结果,则将元素设置为该类型的最小值或最大值。
1 | and_vec v0,v1,v2 |
同样,有和没有补码的逻辑运算。请注意,VECE未使用。
shli_vec v0,v1,i2
shls_vec v0,v1,s2
将所有元素从v1移至标量i2 / s2。即
1 | for(i = 0; i <VECL / VECE; ++ i) |
1 | shri_vec v0,v1,i2 |
对于逻辑和算术右移也是如此。
shlv_vec v0,v1,v2
将元素从v1移到v2。即
1 | for(i = 0; i <VECL / VECE; ++ i) |
shrv_vec v0,v1,v2
sarv_vec v0,v1,v2
对于逻辑和算术右移也是如此。
cmp_vec v0,v1,v2,cond
按元素比较向量,将-1表示为true,将0表示为false。
bitsel_vec v0,v1,v2,v3
按位选择,v0 =(v2&v1)| (v3&〜v1),遍及整个向量。
cmpsel_vec v0,c1,c2,v3,v4,cond
根据比较结果选择元素:
1 | for(i = 0; i <n; ++ i){ |
注意1:当已知最后一个操作数为常量时,会定义一些快捷方式(例如,addi用于add,movi用于mov)。
注意2:使用TCG时,绝不能直接生成操作码,因为其中某些操作码可能无法用作“实际”操作码。 始终使用函数tcg_gen_xxx(args)。
4. 后端
tcg-target.h包含目标特定的定义。 tcg-target.inc.c包含目标特定代码; 它由tcg / tcg.c包含,而不是独立的C文件。
4.1 假设
目标字大小(TCG_TARGET_REG_BITS)预计为32位或64位。期望指针具有与word相同的大小。
在32位目标上,所有64位操作都将转换为32位。必须执行一些特定的操作才能允许它(请参阅add2_i32,sub2_i32,brcond2_i32)。
在64位目标上,使用以下操作在32和64位寄存器之间传输值:
- trunc_shr_i64_i32
- ext_i32_i64
- extu_i32_i64
它们可确保在将值从32位寄存器移到64位寄存器时正确截断或扩展这些值,反之亦然。请注意,trunc_shr_i64_i32
是可选操作。如果满足以下所有条件,则无需实施:
- 64位寄存器可以保存32位值
- 64位寄存器中的32位值不需要保持零或符号扩展
- 所有32位TCG操作都忽略了64位寄存器的高位
此版本不支持浮点运算。代码生成器的先前版本完全支持它们,但是最好先集中于整数运算。
4.2 约束条件
类似于GCC的约束用于定义每条指令的约束。此版本不支持内存限制。别名在输入操作数中与GCC一样指定。
即使没有显式别名,同一寄存器也可用于输入和输出。如果操作扩展到多个目标指令,则必须注意避免破坏输入值。支持GCC样式的“早期内容early clobber”输出,并带有’&’。
目标target可以定义特定的寄存器或常量约束。如果操作使用不允许所有常量的常量输入约束,则它还必须接受寄存器以进行回退。一般将约束“ i”定义为接受任何常数。约束“ r”不是通用定义的,但是每个后端始终使用它来表示所有寄存器。
movi_i32
和movi_i64
操作必须接受任何常量。
mov_i32
和mov_i64
操作必须接受任何相同类型的寄存器。
ld / st / sti
指令必须接受带符号的32位常量偏移量。这可以通过保留特定寄存器来实现,如果偏移太大,则可以在其中计算地址。
ld / st
指令必须接受任何目标(ld)或源(st)寄存器。
如果sti指令无法存储给定常量,则它可能会失败。
4.3 函数调用假设
- 参数和返回值的唯一支持类型为:32位和64位整数和指针。
- 堆栈向下生长。
- 前N个参数在寄存器中传递。
- 下一个参数通过将它们存储为words在堆栈上传递。
- 调用期间某些寄存器被破坏。
- 该函数可以在寄存器中返回0或1的值。 在32位目标上,函数必须能够为64位返回类型返回寄存器中的2个值。
5 建议编码规则
使用全局变量来表示QEMU CPU状态的经常修改的部分,例如整数寄存器和条件代码。 TCG将能够使用宿主机器寄存器来存储它们。
避免将全局变量存储在固定寄存器中。它们只能用于存储指向CPU状态的指针,也可以用于存储指向寄存器窗口的指针。
使用临时变量。仅在真正需要时使用本地临时变量,例如当您需要在跳转后使用一个值时。本地临时对象在当前TCG实现中带来了性能上的损失:它们的内容在每个基本块的末尾保存到内存中。
不再使用的免费临时文件和本地临时文件(tcg_temp_free)。由于tcg_const_x()还会创建一个临时文件,因此在使用它后应将其释放。释放临时文件不会生成更好的代码,但是会减少TCG的内存使用量和翻译速度。
不要为复杂或很少使用的guest指令而使用帮助函数。使用TCG实现guest指令时,使用大约二十多个TCG指令几乎没有性能优势。请注意,这种经验法则更适用于执行复杂逻辑或算术的帮助函数,其中C编译器有能力很好地进行优化。在指令主要执行加载和存储的情况下,它就不那么重要了,在那些情况下,对于较长的序列,内联TCG可能仍然更快。
exec-all.h中的MAX_OP_PER_INSTR设置了每个来宾指令可生成的TCG指令数量的硬限制-不能超过此上限,而不会冒缓冲区溢出的风险。
如果您知道TCG无法证明给定的全局变量在给定的程序点处“死亡”,则使用“丢弃”指令。 x86 guest虚拟机使用它来改善条件代码的优化。
- 本文链接:https://dowob.cn/2020/02/22/%E7%BF%BB%E8%AF%91qemu-tcg-%E4%BB%8B%E7%BB%8D/
- 版权声明:本站所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!