QEMU TCG 的翻译块与执行循环
一、TCG 动态翻译与代码复用
TCG(Tiny Code Generator)把客户机指令转换为宿主机能够执行的代码,并缓存翻译结果。客户机再次运行到同一段程序时,QEMU 可以复用这些代码;循环、函数调用和分支因此不必每次重新译码。缓存的有效性取决于指令内容、执行模式和地址映射,代码改写或运行环境变化时需要重新检查。
本文沿一个三块循环展开:第一次执行 A 时查找缓存,未命中便翻译;执行到 B 后决定回到 A 还是进入 C;循环重复时复用并链接已有代码;程序改写 A 后撤销旧翻译。客户机访存、异常与多 vCPU 同步都围绕这条执行路径解释。
源码采用 QEMU 10.2.0,以 x86 系统模拟和 x86-64 宿主为例。TCG 在用户态实现客户机 CPU 的指令语义、特权状态和访存检查;KVM 的硬件执行路径可对照KVM vCPU 的执行循环与退出处理。两种加速器共享许多机器与设备模型,CPU 的执行机制各有不同。
译码器、IR 优化器、宿主代码生成器以及生成的代码都运行在 QEMU 宿主用户态中。客户机寄存器保存在每个 vCPU 的 CPUArchState,翻译好的机器码保存在 QEMU 的代码缓存。即使客户机与宿主都是 x86-64,TCG 执行的仍是按客户机语义生成的宿主代码。
源码版本:QEMU 10.2.0。
二、从当前 PC 到下一个翻译块
先固定一个简单循环:A 读取整数并加一,B 判断是否继续循环,C 处理循环结束后的工作。设第一次执行时,客户机 RBX 指向的 32 位整数是 41,A 的 mov eax, [rbx]; add eax, 1 执行后,客户机 EAX 应为 42。A、B 位于同一个 4 KiB 代码页,C 位于下一页。下列地址范围用于演示缓存与跨页关系,包含指令和填充字节;交互使用相同布局。
代码页 0x1000
A:0x1000—0x101f 读取 [RBX],EAX 加 1,跳到 B
B:0x1fe0—0x1fff 测试循环条件;成立跳 A,否则顺序进入 C
代码页 0x2000
C:0x2000—0x201f 处理循环之外的工作,再跳到 B
一条执行轨迹:A → B → A → B → C → B
↑
这里的分支结果发生变化
cpu_exec_loop() 以客户机 PC 与执行上下文查找 TranslationBlock(TB,翻译块)。第一次找不到 A,就由 tb_gen_code() 生成宿主代码;随后 cpu_tb_exec() 跳入这些代码。A 结束时选出下一个 PC,执行流继续查找或跳往 B。
| 执行阶段 | TCG 处理 | 相关机制 |
|---|---|---|
| 第一次 A → B | 分别生成 A、B;满足条件时把 A 的出口连到 B | 指令译码、IR、宿主代码、TB 保存的条件 |
| 再次 B → A → B | 查找已有 A,建立 B → A;随后同页循环可直接跳转 | 缓存复用与直接链接的成本差异 |
| B → C,跨页 | 按新 PC 查找 C;没有 C 才翻译 | 跨页分支为何保留查找,缓存仍然有效 |
| 执行 A 中的内存读取 | 经软件 TLB 访问 RAM,必要时进入 helper | 客户机地址转换、MMIO 与异常位置恢复 |
| 客户机改写 A 的指令 | 使重叠 TB 失效、拆除相关链接,再按新字节翻译 | 代码页追踪与旧宿主代码的撤销 |
这五步也给出了性能分析的分界:首次翻译、缓存查询、热代码执行、访存慢路径、代码失效。先确定时间花在哪一步,再决定增加缓存、改善访存还是减少重复翻译。
三、第一次执行 A:从指令译码到宿主机器码
1. 缓存未命中时的三阶段翻译
翻译分为三个阶段。客户机前端理解指令编码和体系结构语义,把操作表示为 TCG 中间表示(IR);公共代码优化 IR、分析临时变量的生命周期并分配宿主寄存器;宿主后端输出可执行机器码。target/i386/tcg/ 是 x86 客户机前端,tcg/i386/ 是 x86 宿主后端,目录名相似但职责不同。
Mermaid 源码
flowchart TB
accTitle: TCG 的翻译与运行路径
accDescr: 缓存未命中时,客户机前端将 x86 指令译为 TCG IR,公共优化与寄存器分配后由宿主后端输出代码。运行时 CPUArchState 提供寄存器状态,生成代码通过软件 TLB 或 helper 访问客户机内存与设备。
subgraph compile[缓存未命中时翻译]
direction LR
G[客户机指令字节<br/>x86 前端译码] --> I[TCG IR<br/>TCGOp / temps]
I --> O[优化与活跃性分析<br/>寄存器分配]
O --> H[宿主后端生成代码<br/>TB.tc.ptr]
end
compile --> E[运行宿主代码<br/>可重复执行]
V[CPUArchState<br/>寄存器与运行状态] <--> E
E --> M{客户机访存}
M -->|软件 TLB 命中普通 RAM| R[宿主 RAM 映射<br/>load / store]
M -->|慢路径| C[访存 helper<br/>页表 / 权限 / MMIO]
E --> F[指令语义 helper<br/>复杂操作或异常]
classDef build fill:#e9f4ef,stroke:#3b7c65,color:#203d31
classDef state fill:#eaf0f9,stroke:#6684ab,color:#263c56
classDef slow fill:#f5efe2,stroke:#9d8152,color:#514328
class G,I,O,H,E build
class V,R state
class C,F slow
x86_translate_code() 进入公共 translator_loop(),后者逐条调用 x86 译码回调。遇到改变控制流的指令、页边界、调试限制或翻译预算限制时结束本块。TB 通常覆盖一段顺序执行的客户机指令,但不要求恰好等于编译器定义的完整基本块;例如单步调试可以让一个 TB 只有一条指令。
2. 把 A 的三个操作逐层展开
下表展开 A 中的读取、加一和跳转。IR 和宿主序列为语义示意,省略寄存器分配、标志位和访存慢路径细节:
| 客户机 x86-64 指令 | TCG IR 表达的操作 | 宿主 x86-64 代码承担的工作 |
|---|---|---|
mov eax, [rbx] | qemu_ld 读取客户机地址,写入 RAX 的低 32 位并清零高位。 | 检查软件 TLB,命中普通 RAM 后使用宿主 load;否则进入访存 helper。 |
add eax, 1 | 32 位加法、结果扩展,以及后续计算条件码所需的信息。 | 执行算术并保存必要结果;宿主寄存器由分配器决定,不要求使用宿主 EAX。 |
jmp B | 同页且满足条件时生成 goto_tb 跳转槽及未链接时的退出路径。 | 先跳到块尾的退出代码;建立链接后,跳转槽指向 B 对应的宿主代码。 |
同一条客户机指令可以展开为多条 IR 和多条宿主指令;复杂指令还可能调用 C helper。反过来,常量传播、无用操作删除等优化也会消除部分 IR。tcg_gen_code() 中依次执行优化、可达性与活跃性分析,再进行寄存器分配和代码生成,因此三层之间没有固定的一对一关系。
四、重复执行 A、B:缓存查找与直接链接
1. 第一次选中目标:查询或生成 TB
cpu_exec_loop() 处理待决事件后构造 TB 查询条件。缓存未命中时,tb_gen_code() 分配 TB 和代码空间,锁定涉及的代码页,调用前端与后端,再登记恢复索引、页关联和全局查询表。cpu_tb_exec() 最终通过 tcg_qemu_tb_exec() 进入宿主代码。返回值中包含最后退出的 TB 和出口编号;如果中途经过了链接块,这个 TB 可能已经不是最初进入的那个。
Mermaid 源码
flowchart TD
accTitle: TB 的查找、生成、链接与失效
accDescr: 主循环查询每 vCPU 跳转缓存与全局 QHT。未命中时翻译并发布 TB,命中则执行缓存代码。同页静态分支可修补为直接链接,其他合适分支通过 helper 查询。代码改写使相交 TB 失效,同时清理缓存并撤销入链与出链。
L[主循环<br/>处理事件与构造查询条件] --> Q{跳转缓存 / QHT<br/>存在匹配的 TB?}
Q -->|未命中| T[译码与代码生成<br/>发布恢复索引、页关联和 TB]
Q -->|命中| E[执行宿主代码]
T --> E
E --> B{块尾路径}
B -->|已有同页静态链接| E
B -->|同页出口首次返回| P[主循环找到目标 TB<br/>修补跳转槽并登记入链]
P --> E
B -->|跨页 / 间接分支快路径| H{helper 查询目标}
H -->|命中 / goto_ptr| E
H -->|未命中| L
B -->|退出请求 / 事件| L
W[写入覆盖客户机代码] --> X[标记相交 TB 失效<br/>移出查询结构与页关联]
X --> U[撤销入链与出链<br/>来源跳转恢复原始出口]
U -.-> Q
classDef active fill:#e9f4ef,stroke:#3b7c65,color:#203d31
classDef lookup fill:#eaf0f9,stroke:#6684ab,color:#263c56
classDef stale fill:#f5efe2,stroke:#9d8152,color:#514328
class T,E,P active
class L,Q,B,H lookup
class W,X,U stale
2. 同页循环:把 A、B 的出口直接连起来
同页静态分支预留 goto_tb 槽。第一次经过该出口时,槽仍指向本块的原始退出路径,执行流回到主循环。主循环找到目标 TB 后,tb_add_jump() 将槽修补到目标的宿主代码入口,并把来源挂到目标的入链表。下一次经过相同出口便可直接进入目标块,省去主循环和目标查询。
translator_use_goto_tb() 要求分支目标与当前 TB 的起始 PC 位于同一客户机虚拟页,且没有禁止链接;系统模拟主循环还拒绝将该直链指向跨两个代码页的 TB。跨页映射可以独立变化,未经重新查询就保留跳转容易运行到旧代码。断点、单步和需要重新检查中断的状态变化也会限制链接。
3. B 转向 C:跨页查找仍可复用代码
跨页或间接跳转仍有另一条快路径:lookup_and_goto_ptr 调用 helper_lookup_tb_ptr(),按新的 CPU 状态查询目标。命中就返回目标代码入口,由生成的 goto_ptr 继续执行;未命中才回到主循环生成代码。因此应区分三种成本:翻译新块、查询已有块、沿已修补链接直接跳转。缓存命中不等于已经建立直链,跨页也不等于每次重新翻译。
下面固定执行模式和地址映射,用 A、B、C 三个块观察这些路径。A 与 B 在同一页,C 位于另一页;“改写 A/B 指令”修改与这两个块相交的代码范围,C 保持有效。演示一次推进一个 TB,真实执行会连续运行,直到退出请求、异常或其他条件需要返回调度循环。
跟随 PC,观察 TB 查找与链接
A、B 位于同一代码页,C 位于下一页。每次推进执行一个 TB;先沿 A → B → A 建立链接,再比较跨页分支和代码改写。
代码页 0x1000
代码页 0x2000
已建立的直接链接
B → C、C → B 跨越代码页,使用查找路径,不建立这里的直接链接。
操作记录
首次执行 A、B 时,查找未命中并生成对应 TB,同时建立 A → B 的直接链接。再次进入 A 可命中已生成的 TB,并建立 B → A;后续同页跳转可沿链接执行。B 的指令位于 0x1fe0—0x1fff,条件不成立时顺序进入 0x2000 的 C。改写 0x1000—0x1fff 的代码页会使 A、B 失效并解除链接;C 保留。启用 JavaScript 可逐步观察。
示例采用 x86 系统模拟、4 KiB 代码页,两页分别映射到不同的 RAM 后备页,映射与翻译上下文固定;每个 TB 的指令范围为 32 字节,F0/C0 表示固定的 flags/cflags。每一步只展示入口选择和下一 PC,不执行真实指令,也不以步数衡量性能。代码改写发生在两次执行之间;失效项保留在图中便于比较,实际查找不会返回它们。
直链循环仍需响应退出请求。普通 TB 的入口代码检查退出标记,主循环再处理相应中断或工作;开启 icount 时还有指令预算。TB 长度、链接策略与这些检查共同影响调度开销,不能单凭“链接越长”判断运行速度。
五、缓存依据:TB 保存代码,CPU 状态保存运行值
A 的宿主代码生成后,需要同时保留“执行哪个入口”和“在什么条件下可以复用”两类信息。EAX 这次计算出的数值属于当前 vCPU;A 的指令字节、执行模式与生成代码则决定缓存是否有效。
| 结构 | 关键内容 | 职责 |
|---|---|---|
CPUArchState / CPUX86State | regs[] / eip / hflags / cc_op / cc_dst | 某个 vCPU 的客户机寄存器、执行模式和延迟条件码状态。 |
TCGContext | ops / temps / gen_tb / code_gen_ptr | 当前翻译过程的 IR、临时变量、目标 TB 与代码输出位置。 |
TCGOp | opc / args[] / life / link | 一条 IR 的操作码、参数、生命周期信息及操作链表连接。 |
TranslationBlock | pc / cs_base / flags / cflags / page_addr[] | 记录生成代码所依据的执行环境及 RAM 代码页;绝对 pc 字段用于非 CF_PCREL 路径。 |
TranslationBlock.tc | ptr / size | 已生成宿主代码的入口与长度;TB 的 size / icount 则记录客户机代码长度和指令数。 |
| TB 跳转与恢复信息 | jmp_dest[2] / jmp_list_head / jmp_reset_offset[2] | 最多两个可修补出口、所有入链及原始目标;宿主代码后另存指令位置恢复数据。 |
TB 缓存代码,CPUArchState 保存每个 vCPU 的运行值。例如前端把客户机通用寄存器定义为相对于 tcg_env 的全局变量;生成代码通过当前 env 访问它们。第一次读到 41,A 得到 42;另一次读到 99,同一个有效 TB 就应得到 100,无需为数据值变化重新翻译。只要代码与执行上下文匹配,不同 vCPU 也可以执行同一个 TB,各自修改自己的寄存器状态。
TB 查找的地址与执行状态条件
相同 PC 不一定能复用同一个 TB。x86 的 x86_get_tb_cpu_state() 提取线性 PC、CS 基址,以及由 hflags 和部分 EFLAGS 组成的 flags。32/64 位模式、特权级、单步等状态会影响译码与生成逻辑;cflags 还包含并行执行、指令数限制和链接控制等翻译条件。普通通用寄存器的运行值则不会整体塞进缓存键。
每个 vCPU 先查自己的 tb_jmp_cache,未命中再查全局 QHT。全局查询还要确认实际代码位于哪一段 RAM,防止相同虚拟 PC 映射到不同代码后误用旧块。本文版本的 x86 系统模拟默认采用 CF_PCREL 相对 PC 代码,因此满足其他条件时,同一段 RAM 代码可以在不同虚拟映射下复用;未采用该模式时还要比较绝对 PC。这里的 RAM 地址是 QEMU 内部地址,不是 HPA。
无论怎样复用,客户机的当前 PC 都必须正确。直链节省的是目标查找与主循环往返;x86 生成代码仍按需要更新 eip。具体缓存键与更新位置可对照文末的 cpu-exec.c 和 gen_jmp_rel() 源码。
六、A 读取内存:软件 TLB、RAM 与设备慢路径
生成代码中存在两类不同的内存操作。访问 env 中的寄存器状态是 QEMU 自身的宿主内存访问;客户机的 [rbx] 访存使用 qemu_ld / qemu_st,携带客户机地址、访问宽度、字节序和 MMU 索引等信息,需要遵守客户机权限及地址转换规则。
系统模拟的软件 TLB 缓存客户机虚拟页到 RAM 宿主映射及访问属性。普通 RAM 命中时,生成代码可快速计算宿主地址并完成 load/store;缺页、权限不符、跨页、监视点、代码写保护或 MMIO 等情况进入相应慢路径。软件 TLB 未命中会调用客户机架构的地址翻译逻辑;确实触发客户机页故障时,才交付客户机异常。
MMIO 最终到达 QEMU 的 MemoryRegion 和设备回调,仍需满足设备的锁与执行上下文约束。TCG 执行大量普通 RAM 访问时不需要每次进入设备模型;访问设备寄存器也不能直接当作普通宿主内存读取。地址空间、RAM 与 MMIO 的对应关系可继续阅读QEMU 内存初始化与地址空间管理。
翻译缓存和软件 TLB 是两个层次:前者复用指令的宿主代码,后者缓存每次访存所需的地址转换。客户机更改页表并完成必要的 TLB 维护后,代码查询必须基于新的映射;映射回原来的 RAM 代码且上下文匹配时,尚未失效的 TB 仍可能被复用。
七、A 的读取发生异常:恢复准确的客户机位置
假设示例块中的第一条 load 触发客户机页故障:这一轮尚未取得 41,也没有执行加一,因此客户机应停在 load 对应的指令位置,EAX 保留本次读取前的值。生成代码为减少内存访问,可能把部分状态留在宿主寄存器中,或延迟物化 x86 条件码;异常恢复需要确定块内实际执行到的位置。
前端在每条指令开始时生成 insn_start 记录。x86 保存当前客户机 PC 信息及 cc_op;代码生成阶段再关联实现该指令的宿主代码边界。encode_search() 把这些信息压缩存放在宿主代码之后。异常 helper 通过宿主返回地址找到 TB,再沿恢复表确定故障对应的客户机指令。
cpu_restore_state_from_tb() 解码恢复数据,调用 x86 的 restore_state_to_opc() 设置 eip,必要时恢复条件码操作类型;随后异常退出路径回到预先建立的 setjmp 边界,由执行循环处理异常。这个表保存的是重建准确位置所需的信息,不是每条指令执行前的完整寄存器快照。通用寄存器及内存更新的正确时序,仍由翻译器、寄存器分配和 helper 的异常约定共同保证。
八、A 的指令被改写:失效旧 TB 并撤销链接
客户机内核打补丁、JIT 生成代码或普通程序修改可执行区域后,缓存的宿主代码可能已经过期。系统模拟通过软件 TLB 的慢写路径跟踪代码页写入;notdirty_write() 检查代码脏标志,必要时调用 tb_invalidate_phys_range_fast()。
PageDesc 连接着涉及该 RAM 页的 TB。失效代码按写入范围检查 TB 覆盖的指令字节,处理相交的块。小范围写入不必无条件使同页所有 TB 失效,某些较宽的失效入口则可以覆盖更大的范围。上述交互同时改写 A、B,是因为写入范围与两者都相交。
- 在跳转锁保护下设置
CF_INVALID,阻止新的入链连接到旧 TB。 - 从全局 QHT、页关联及各 vCPU 的跳转缓存中移除对应记录。
- 解除旧 TB 的出链,并遍历它的入链表,将来源跳转槽恢复到原始退出路径。
- 下次执行相关客户机地址时重新查找,必要时根据新字节翻译。
如果一个 x86 TB 正在改写自己覆盖的代码,QEMU 还要避免继续执行块内已经翻译好的旧指令。精确自修改代码路径利用宿主返回地址恢复当前位置,退出当前 TB,并请求下一次以一条指令的 TB 重新执行。这与只删除哈希表记录不同:正在运行的控制流也需要妥善退出。
单个 TB 失效主要撤销复用和链接资格,不立即把所有宿主代码空间重新分配出去。代码缓冲区耗尽时,QEMU 使用安全工作机制协调全局 flush,清空翻译缓存并重置代码区域。增大 tb-size 可能减少容量导致的重新翻译,但会增加内存占用,也无法避免真正的代码改写失效。
九、扩展到多 vCPU:共享代码与同步边界
MTTCG 为每个 vCPU 创建宿主线程,线程拥有自己的 CPU 状态、跳转缓存和翻译上下文。运行普通翻译代码时释放 BQL;进入需要全局串行化的设备或控制路径时,再按相应规则同步。QEMU 10.2 的 x86 前端声明支持 MTTCG,默认自动模式会在未启用 icount 时选择它;显式 thread=single 则保留轮转执行方式。
全局 TB 查询表和已生成代码仍可共享。并行翻译使用各线程的上下文与代码区域,同时通过代码页锁、QHT 插入和跳转锁保证发布与失效安全。如果两条线程生成了等价 TB,发布阶段可以保留已有结果、丢弃重复翻译。增加 vCPU 线程后,代码改写、跨 vCPU TLB 操作和全局 flush 的协调成本也随之增加。
客户机 CPU 的并行还要求保持它的内存模型。x86 前端声明允许普通 Store→Load 重排,而其他规定的顺序要维持;tcg_gen_req_mo() 比较客户机要求与宿主默认保证,生成缺少的屏障。带 LOCK 的原子读改写不能拆成让其他 vCPU 任意插入的普通 load/store。遇到后端无法直接支持的原子操作,QEMU 可通过 EXCP_ATOMIC 退出,在排他执行区内用单指令 TB 完成,再恢复并行执行。
每个 TB 有多个客户机指令,不代表整个 TB 是一笔原子事务。并行 vCPU 可以观察到客户机内存模型允许的中间状态;QEMU 内部的页锁和跳转锁保护翻译元数据,也不代替客户机程序自己的锁与屏障。
十、TCG 的演进与性能取舍
1. 从 dyngen 到 TCG,再到多线程执行
2008 年引入 TCG时,Fabrice Bellard 明确提出要减少不同 GCC 版本带来的问题,并改善代码生成。早期 dyngen 依赖预先编译的微操作片段;TCG 逐步改为由公共 IR 和宿主后端直接组织代码,使优化与寄存器分配拥有更明确的接口。代价是每种宿主后端都要实现、验证指令编码及相关约束。
TB 缓存和直接链接减少了热代码的重复工作,但引入了代码页追踪、跳转撤销和精确异常恢复。较大的块可以摊薄入口开销,也会增大生成成本和失效时的重做范围;复杂优化若明显拖慢首次翻译,对短生命周期程序未必划算。这些取舍解释了 TCG 为什么围绕有界 TB 持续优化,而不简单追求把整段程序提前编译完。
2017 年的 MTTCG 设计进一步把系统模拟从单线程轮转推向每 vCPU 一线程。旧模型天然串行化了许多共享访问;并行后,原子指令、内存屏障、TB 发布与清理都要获得明确的同步规则。当前实现支持并行翻译和细粒度锁,但设备共享状态、代码改写频率及客户机锁竞争仍会限制扩展性。
2. 沿主路径判断优化是否有效
| 主要耗时 | 改进方向 | 代价与验证 |
|---|---|---|
| 冷启动大量翻译 | 减少重复生成,控制翻译与优化复杂度 | 比较启动时间和进入稳定执行后的耗时;更复杂优化未必能在短任务中回本 |
| 热循环 A ↔ B | 复用 TB、满足条件时直接链接 | 检查查找 / helper 占比与真实吞吐;链接仍需退出检查和正确失效 |
| A 的 load 经常走慢路径 | 区分软件 TLB 未命中、权限、跨页与设备访问 | 扩大 TB 缓存不能直接解决客户机访存或设备延迟 |
| 改写代码后反复翻译 | 确认是否真实代码更新或代码缓冲区容量不足 | 增加 tb-size 可缓解容量 flush,却不能跳过必要的代码失效 |
| 增加 vCPU 后收益有限 | 检查客户机锁、共享设备、原子慢路径和全局协调 | 同时比较线程 CPU、吞吐与延迟,不能以线程数量代替扩展性 |
在宿主启动测试 QEMU 时,选择下面的 TCG 加速器和日志参数;已有的 KVM 加速器配置应先移除,避免同时指定两种执行方式。日志输出位于宿主的 /tmp/qemu-tcg.log:
-accel tcg,thread=single \
-d in_asm,op,op_opt,out_asm \
-D /tmp/qemu-tcg.log
in_asm 展示客户机指令,op 与 op_opt 展示优化前后 IR,out_asm 展示生成的宿主代码。对 A 的 load/add/jump,可以沿这几段核对翻译过程。它们主要记录代码生成,热循环复用既有 TB 时未必再次输出,因此日志条数不能直接换算为执行次数;详细日志也会改变运行速度。
对较大工作负载,-perfmap 或 -jitdump 能帮助宿主 perf 识别 JIT 代码。分析时还应查看 helper 和设备路径的采样,避免把所有耗时都归因于翻译器。TCG 的优势是跨架构执行、软件可观测性和 CPU 语义模拟;需要同架构生产负载吞吐时,应结合硬件虚拟化路径比较。
十一、参考资料
- 公共翻译循环与同页链接条件、x86 前端回调
- TranslationBlock 与跳转链表、TCGOp 与 TCGContext
- CPUX86State、客户机寄存器对应的 TCG 全局变量
- x86 系统模拟默认启用 CF_PCREL、x86 查询状态与异常状态恢复、TB 缓存键和两级查询
- TB 分配与翻译、代码发布与重复 TB 处理、优化、活跃性分析与寄存器分配
- helper 查询与进入宿主代码、跳转槽修补、执行循环
- x86 块尾、goto_ptr 与 goto_tb、TB 入口退出请求检查
- 宿主 x86 后端的 qemu_ld、代码页慢写入口
- 失效标记、缓存清理与撤链、写入范围检查与精确自修改代码处理
- 恢复表编码及宿主 PC 定位、安全的全局 TB flush
- MTTCG 线程循环、默认线程模式选择
- x86 客户机内存模型、补充访存屏障、原子操作的排他执行回退
- 翻译器内部机制、TCG IR 与操作语义、多线程 TCG 设计