跳转到内容

翻译原理

Champollion 只做用户态。客户程序不是被模拟出来的机器,它就是宿主进程本身。rvrun 加载 ELF, 把各个段映射进预留的客户地址空间,然后从入口点开始执行。当程序发起系统调用时,由运行时负责 处理——能直接实现的用 Rust 实现,无法实现的则翻译给宿主。

加载器解析 ELF,把各个段映射进客户内存(预留 256 GiB;在 macOS 上客户地址是相对该区域的固定 偏移,在 arm64 Linux 上客户地址就是宿主地址),构建初始的 Linux 栈,并为静态 PIE 映像做重 定位。程序若是动态链接的,它的 PT_INTERP——musl 的 ld-musl-riscv64.so.1——会先运行, 从 -L 指定的 sysroot 中解析。

解释器逐条取指、译码、执行,它是永久的基准:其他引擎均以它为准进行比对,而不是反过来被信任。快速引擎 维护解码块缓存,块与它最后两个后继链接在一起,另有每线程的软件 TLB,覆盖客户内存的 4 KiB 页面。 内存映射发生变化或客户执行 fence.i 时,缓存会被丢弃。

riscv-tests 测试套件会让快速引擎和 JIT 与解释器逐例比对,要求每个用例结束时状态完全一致。

基本块由手写的编码器翻译成 arm64,它的输出在测试中与汇编器逐字节核对。生成的代码把客户地址 掩码进预留区域,并通过跳转缓存找到已翻译的后继块。翻译结果按可执行文件保存在持久化代码缓存中 (默认在 ~/Library/Caches/champollion),因此程序第二次运行时就已经是翻译好的; RVRUN_CACHE=0 可以关掉,RVRUN_JIT=0 则退回解释器。

客户程序的系统调用遵循 Linux ABI。一部分由运行时自己处理(mmap、brk 等);其余的通过 一层极薄的 HostOs 接口过界。它的 macOS 后端负责翻译差异——open 的标志位、errno 取值、 信号、flock;它的 Linux 后端则把调用直接传给内核,内存布局为恒等映射。两种情况下错误都以 Linux errno 返回,并由一个宿主线程向客户程序投递信号。