存储单元模拟
翻译信息的存放
二进制翻译完成指令的基本翻译后,还要把翻译完的信息(源机器平台中寄存器和其状态)部署在目标机器上(实现镜像),等价模拟源平台的寄存器和内存等存储单元
镜像部署的解决方案
(1)开辟一块内存空间做镜像映射
开辟内存空间的方法屏蔽了寄存器使用差异, 实现简单
缺点:会引发大量的访存代价
(2)利用硬件支持寄存器映射
优点:效率较高,
缺点:但不同处理器平台中寄存器数量和使用约定各异, 很难实现寄存器的全部映射. 尤其是源平台寄存器数量多于目标平台时, 必然有部分源机器平台中的寄存器要被映射到内存单元中, 涉及寄存器映射收益和映射代价选择问题
寄存器的映射收益和映射代价选择问题(寄存器的优化)
其他寄存器优化方法
例子:x86_64平台有16个通用寄存器,riscv64平台有32个通用寄存器。如果是riscv64到x86_64的二进制翻译,即源平台是riscv64、目标平台是x86_64,那么目标平台的通用寄存器数量就少了16个,没法一对一映射
(1) 针对源平台寄存器数量多于目标平台的情况, 提出将目标平台向量寄存器的低位与源平台寄存器进行映射, 缓解目标平台寄存器数量不足的问题
PS:如果不使用映射技术,向量寄存器的低位主要用于存放直接参与计算的核心数据、临时结果、指针、状态信息以及一些常量,总之寄存器的低位非常宝贵,放的都是常用的东西,以便减少上下文切换和数据传输,节省时间
映射:指将一个平台的寄存器使用方式调整为适应另一个平台的一种策略
该技术:将源平台的寄存器内容映射到目标平台的向量寄存器低位部分
即,直接把源平台的寄存器放到目标平台的核心位置,减少上下文切换和数据传输
(2)根据不同算法确定寄存器优先级, 以保证寄存器资源的利用率
(3)对基本块内中间指令的寄存器需求次数进行排序, 优先为排序靠前的变量分配寄存器
这样可以高寄存器的利用率,减少内存访问的频率
(4)对代码映射区域进行访存频率排序, 提出优先对引发高频率访存的代码区域执行寄存器映射, 保证了寄存器映射资源的利用率最大化
(5)根据程序对不同寄存器的使用频率确定寄存器分配需求优先级, 并在寄存器分配过程中根据优先级动态的调整分配顺序
(6)引入启发式寄存器匹配算法和用于性能上界估计的穷举搜索算法来指导寄存器
的优先级, 选择最佳的寄存器映射配置
一种优秀的寄存器映射策略应该尽可能地将程序变量保存在寄存器中, 减少对内存的访问
内存地址的翻译
与存储单元模拟相关的另外一个难点是对内存地址的翻译
(1)需要经过两级虚实地址转换
QEMU 采用软件模拟实现两级虚实地址转换翻译, 翻译效率较低
(2)将客户虚拟地址空间嵌入到主机地址空间中, 宿主机可无差别地直接访问客户机虚
地址. 然而, 该方法对于客户机和宿主机的虚拟地址空间大小有严格限制
BTMMU通过扩展内存单元部件, 在内核模块中增加影子页表来实现内存地址翻译, 具有通用性.
LAT采用硬件支持的方式实现客户机虚地址到宿主机实地址的直接代换, 彻底消除软件模拟虚实地址转换引发的性能开销问题.
原子操作时序维护
原子操作保证了资源访问的互斥性和访问时序
原子操作翻译过程中首先面临的一个挑战是原子指令的等价翻译
原子指令:
CAS指令
x86 平台有将近 20 条原子指令, 如
inc、xadd 原子操作以及 cmpxchg 比较并交换 (compare and swap, CAS) 指令
LL/SC 指令对
MIPS、ARM、Alpha 等平台并没有 CAS 指令, 其对应的是链接加载 (load linked, LL) 和条件存储(store conditional, SC) 指令组成的 LL/SC 指令对
CAS 原子指令的翻译方法
(1) 将源程序中所有的内存写操作转为原子写, 以保证访问内存数据的一致性. 但是这种做法对性能影响大
(2) 维持源程序逻辑, 利用 CAS 和 LL/SC 指令对进行功能的等价模拟,,即把LL/SC用CAS表示出来
从 LL/SC 到 CAS 翻译遇到的问题
正确性问题
但是方法二存在从 LL/SC 到 CAS 翻译的正确性问题
解决方法:提出基于内存事务解决多线程应用之间的数据竞争问题, 将元数据访问封装在一个事务性原子块内完成
ABA 问题
还可能会引发 ABA 问题
解决方法:
1.利用 Helper 函数软件模拟实现 LL/SC 锁指令翻译
2.无锁队列引用计数的内存保护方法, 只有内存引用节点计数值为 0 时才允许访问该内存
3.通过位图维护 Cache 行和哈希完整地址行检查来避免数据竞争并保证数据访问的原子性
4.利用插桩的方式维护一个非阻塞哈希表来记录更新内存, 保证了锁变量地址与被访问内存地址的一致性.
地址非对齐问题
x86 平台支持多种地址非对齐的原子操作,但译通常要求先将访存地址对齐,但在不同位宽的原子指令翻译时依旧可能会引发错误
.MIPS、ARM、Alpha 等平台要求 LL/SC 指令满足 32 位地址对齐
解决
利用 LL/SC 指令对模拟 CAS 指令
但是该方法基于软件模拟 CAS 指令, 可能会引发大量的冗余判断.
异常和中断处理
代码块执行时遇到异常或者中断, 其需要调用回退机制,还原源程序状态。
二进制翻译为了实现回退机制, 需要加入额外的保存、恢复机器状态的代码
IA-32 EL采用还原点检测机制, 一旦发生异常便从最近的还原点恢复出精确的机器状态.
Crusoe采用硬件支持的影子寄存器保存精确状态.
然而, 回退机制是比较耗时的,
考虑到中断是异步事件,所以就要考虑是不是可以检查中断异常,避免退回的产生
QEMU 使用主动处理中断的策略在生成翻译代码阶段插入中断检查代码, 避免回退机制产生的开销(检查中断的发生,主动检查)
Captive使用硬件虚拟化在客户模式下运行一个完整的翻译系统, 它的外部中断通过中断控制器传播到客户机系统, 并在中断处理程序中设置中断挂起标志(中断时给出通知,被动接收)
内存一致性保证
处理器对内存模型的支持情况
顺序性内存模型 (sequentially consistent, SC)、
强内存模型(total store order, TSO)
弱内存模型 (weak memory order, WMO)
针对 TSO-to-WMO 并发程序的翻译
同一并发程序在 TSO 和 WMO 两种不同内存模型的处理器中执行的结果
出现不同输出结果的原因是 TSO 模型通过硬件隐式保证访存的时序性, 而 WMO 模型的硬件
并没有进行该操作, 代码执行时访问数据的顺序可以重排
TSO 模型通过硬件保证访存的先后顺序, 而 WMO 模型的硬件无法保证内存的访问顺序
如果想要将 TSO 模型的应用迁移到 WMO 模型, 需要额外加入同步栅栏指令来避免指令发生重排序
在二进制代码中无论是寻找待同步点还是添加栅栏指令均会带来较高的性能开销.
对于事务冲突较少且事务足够大的并发程序, 基于事务的内存一致性处理性能较好.
对于事务开销较高的并发程序, 插入内存同步栅栏指令的效果更好. 然而, 相比于使用单一方法, 两种方法混合使用效果更优.
相比于使用单一方法, 两种方法混合使用效果更优
强化弱内存模型变换的鲁棒性, 当发现违反鲁棒性时再插入适当的内存栅栏指令
在 Coq 工具上设计公理化的宽松内存模型实现内存读写操作的局部重排序, 完成从 SC 内存模型向 WMO 内存模型的变换
内存一致性框架 ArMOR, 构建内存顺序约束表以确定 load→load、load→store、store→load 以及store→store 是否需要保序, 辅助指导翻译程序动态的注入栅栏同步指令
Risotto 的并发程序翻译方法
.Risotto 形式化了基于 TCG IR 中间表示的内存模型转换过程
并使用被形式化的 TCG IR 实现强内存模型到弱内存模型的内存映射. Risotto 很好解决了 QEMU 在不同内存模型的硬件平台之间正确翻译并发程序的难题
代码挖掘
与代码挖掘相关的两个问题包括代码自修改和自生成
代码自修改在 Adobe Premiere、Doom 以及嵌入式应
用中较为常见, 代码自生成则随着 JavaScript、PHP、C#等脚本语言的流行变得更加普遍
当发生代码自修改和代码自生成时, 二进制翻译需要满足:
(1) 及时发现被修改代码块.
(2) 重新翻译被修改代码并更新缓存
静态翻译无法预知代码自修改和自生成,
动态翻译虽然能够及时发现变化的代码,但是在代码块的发现和缓存更新方面开销较大.
动态翻译主要是基于信号探测和代码注释的方法实现
QEMU 采用 mprotect 方法将所有代码设置为不可写,通过监测 SIGSEGV 异常来发现自修改情况
功能等价性研究
逻辑等价性验证面临着大量挑战:
(1) 二进制翻译技术的跨平台特性导致大量测试集依赖于处理器架构, 不同平台之间的回归测试集难以直接复用
(2) 二进制翻译系统对自身的代码改动极其敏感, 开发过程中需要频繁的引入测试. 然而, 当前业界普遍采用 SPEC2006、SPEC2017、LTP 等大型测试集进行测试, 测试过程十分耗时[112]
(3) 二进制翻译系统的功能设计复杂, 测试过程中很难保证对于功能点的全覆盖. 研究发现即使采用大型测试集进行测试, 依旧有 30% 的二进制翻译系统代码功
能点无法被覆盖
(4) 等价性测试主要是基于特定测试集的白盒与黑盒测试, 而要验证翻译程序的功能等价性
是极具困难的. 我们将二进制翻译的功能等价性研究归纳为软件测试和验证研究.
翻译效率提升
例如, 基于 SPEC2006 测试集测试, ExaGear 实现 x86-to-ARM64 的平均翻译效率达 82%; Tango 实现ARM32-to-ARM64 的平均翻译效率达 80%; Rosetta 2 实现 x86-64-to-ARM64 的平均翻译效率超过 75%; LAT实现 x86-to-LoongArch 的平均翻译效率超过 65%
(1) 二进制翻译系统自身在指令翻译、代码优化中引发了大量开销.
(2) 不同硬件平台存在差异, 为实现指令等价翻译和机器状态正确模拟, 二进制翻译时会引入大量冗余操作
(3) 基于虚拟环境模拟的方法, 二进制翻译涉及缓存代码管理、分支跳转地址计算、控制流切换等运行时开销
(4) 翻译过程需精准维护源程序逻辑, 目标平台处理器硬件资源的利用会受到源程序特征的影响. 如目标平台在多核资源的利用上不能改变源程序设置的最大处理器核数.
为了提升翻译效率, 当前已有大量优化研究工作. 例如降低翻译开销的动静结合、多线程优化,减少运行时维护开销的分支查找优化、热路径生成; 提升目标代码生成质量的中间代码优化、指令并行化、寄存器映射; 缩小源平台和目标平台体系结构差异的软硬协同设计高级优化.