翻译原理
是一个进程,不是一台机器
Section titled “是一个进程,不是一台机器”Champollion 只做用户态。客户程序不是被模拟出来的机器,它就是宿主进程本身。rvrun 加载 ELF,
把各个段映射进预留的客户地址空间,然后从入口点开始执行。当程序发起系统调用时,由运行时负责
处理——能直接实现的用 Rust 实现,无法实现的则翻译给宿主。
从文件到第一条指令
Section titled “从文件到第一条指令”加载器解析 ELF,把各个段映射进客户内存(预留 256 GiB;在 macOS 上客户地址是相对该区域的固定
偏移,在 arm64 Linux 上客户地址就是宿主地址),构建初始的 Linux 栈,并为静态 PIE 映像做重
定位。程序若是动态链接的,它的 PT_INTERP——musl 的 ld-musl-riscv64.so.1——会先运行,
从 -L 指定的 sysroot 中解析。
两个引擎,一个基准
Section titled “两个引擎,一个基准”解释器逐条取指、译码、执行,它是永久的基准:其他引擎均以它为准进行比对,而不是反过来被信任。快速引擎
维护解码块缓存,块与它最后两个后继链接在一起,另有每线程的软件 TLB,覆盖客户内存的 4 KiB 页面。
内存映射发生变化或客户执行 fence.i 时,缓存会被丢弃。
riscv-tests 测试套件会让快速引擎和 JIT 与解释器逐例比对,要求每个用例结束时状态完全一致。
基本块由手写的编码器翻译成 arm64,它的输出在测试中与汇编器逐字节核对。生成的代码把客户地址
掩码进预留区域,并通过跳转缓存找到已翻译的后继块。翻译结果按可执行文件保存在持久化代码缓存中
(默认在 ~/Library/Caches/champollion),因此程序第二次运行时就已经是翻译好的;
RVRUN_CACHE=0 可以关掉,RVRUN_JIT=0 则退回解释器。
系统调用与信号
Section titled “系统调用与信号”客户程序的系统调用遵循 Linux ABI。一部分由运行时自己处理(mmap、brk 等);其余的通过
一层极薄的 HostOs 接口过界。它的 macOS 后端负责翻译差异——open 的标志位、errno 取值、
信号、flock;它的 Linux 后端则把调用直接传给内核,内存布局为恒等映射。两种情况下错误都以
Linux errno 返回,并由一个宿主线程向客户程序投递信号。
