豫言操作系统总体架构
状态:架构与实施方案,2026-09-28 审定。本篇是豫言操作系统的总览,取代统一接口方案(2026-09-25)与底层接口方案(2026-09-26)中与之冲突的部分,冲突清单见第十四节;两个接口的逐条规范仍以语言仓库根目录的 豫言操作系统接口/ 与 豫言操作系统底层接口/ 两棵规范树为准。文中以【已实现】【部分实现】【规划】标注成熟度;数字是 2026-09-28 的统计或实测。审定时按审阅意见修订:编译器只保留 Wasm 一个后端,C 后端提前到 WP1 删除;构建工具合并为豫构一个;原生为默认运行方式,Node 为备用。面向读者的愿景介绍见豫言操作系统愿景;文言本见文言版。
一、目标与原则
豫言操作系统是一套版本化的程序装载、执行与系统能力契约,以及实现这份契约的宿主与系统。目标是让同一份中文程序,在浏览器、云端 Worker、Windows / macOS / Linux 桌面,以及我们自己的裸机系统上,得到相同的接口语义。
六条原则,前五条是已经确定的决定,第六条是本篇确立的运行矩阵:
- 豫言优先。 编译器、运行时、shim、平台后端、构建与验证工具都用豫言写。非豫言代码只留给“必须由宿主环境提供”的部分(浏览器与 Node 的 JavaScript 外壳),并逐步降到最少。
- Wasm 是统一执行边界。 豫言应用、编译器、执行器自身都经过 Wasm;不再另建绕过 Wasm 的原生路径作为终局。
- 编译器只有一个后端:Wasm。 不生成 C,不依赖 LLVM、clang、外部汇编器、外部链接器与 libc,不使用 Wasmtime。现有的 C 后端与 C 运行时尽早删除(第十二节 WP1);删除之后、路二的提前编译完成之前,开发工具链以 WasmGC 形式在 Node 上运行。
- 两层接口,窄腰。 应用面用带 GC 的“豫言操作系统接口”,平台面用不带 GC 的“豫言操作系统底层接口”;每个平台只需实现底层接口一次。
- 能力即授权。 应用依赖的能力包一律必需;接口实现与本次运行的资源授权分开检查;宿主拥有更多 API 不等于全部暴露给程序。
- 每个商用平台两条运行路径,原生为默认。 路一:Node.js 加我们的跨平台 Wasm 加一个 JS 文件;路二:我们自己直接生成的原生二进制,AMD64 与 ARM64 两种指令集,使用各平台正确的系统调用。路二跑通后是默认方式;路一是备用方案,在路二跑通之前也用它。
二、总览
自上而下,一个程序经过的各层:
| 层 | 路一(JS 路) | 路二(原生路) |
|---|---|---|
| 源程序 | 高级模式(带 GC)的豫言程序 | 同左;另可运行外来工具链产出的 WASI 程序 |
| 编译器 | 豫言自托管编译器:源码 → 类型检查 → 统一 ANF → Wasm 模型 → 标准 .wasm |
同左 |
| 程序形式 | WasmGC 模块 | WasmGC 模块;或 WASI 模块 |
| 应用面接口 | 豫言操作系统接口(GC 接口),52 个能力包 | 同左;WASI 程序用 WASI preview1 |
| 实现者 | JS 宿主:浏览器、Node.js、Cloudflare Worker | GC shim 与 WASI shim,与执行器同为豫言底层模式程序 |
| 平台面接口 | 无:JS 宿主直接使用 JS 环境的 API | 豫言操作系统底层接口:6 个包,45 个函数,32 位整数,状态码 |
| 进入操作系统 | JS 引擎负责 | Linux 直接发系统调用;macOS 导入 libSystem;Windows 导入 kernel32 与 ntdll;裸机调用自有内核服务 |
| 机器码 | JS 引擎负责 | 我们直接生成 ELF、Mach-O、PE 或裸机镜像,AMD64 与 ARM64 |
读图要点:
- 编译器只产出标准 Wasm。带 GC 的程序产出 WasmGC,底层模式的程序产出只含 32 位整数的 Wasm。
- 路一在“有 JavaScript 引擎”的地方运行:桌面用 Node.js,网页用浏览器,云端用 Cloudflare Worker。JS 宿主直接实现 GC 接口,不经过底层接口。
- 路二不需要任何运行时:执行器(shim)与平台后端被一起编成原生二进制。GC 程序经 GC shim,WASI 程序经 WASI shim,二者都落到底层接口,再由各平台后端发出真正的系统调用。
- 两层接口互不依赖,也互不越级:应用不直接调用底层接口;底层接口只有 GC shim 与 WASI shim 两个使用者。
三、运行矩阵:两条路乘以各平台
| 平台 | 路一:Node.js + 跨平台 Wasm + JS 文件 | 路二:自己直接生成的原生二进制 |
|---|---|---|
| Windows | 【规划】Node 宿主适配 | 【规划】PE32+,x64 与 ARM64,导入 kernel32 / ntdll |
| macOS | 【规划】Node 宿主适配 | 【规划】Mach-O,x86-64 与 arm64,导入 libSystem,带代码签名 |
| Linux | 【规划】Node 宿主适配 | 【规划】ELF64,x86-64 与 arm64,直接发系统调用,静态、无 libc |
| 裸机(自有系统) | 无 | 【已实现,仅 QEMU】ARM64 与 AMD64,内核与用户态均由豫言生成 |
| 浏览器 | 【已实现】20 个能力包有适配 | 无 |
| Cloudflare Worker | 【已实现】33 个能力包有适配 | 无 |
两条路的定义:
- 路一(JS 路)。 发行物是一份
.wasm(WasmGC)、一个 JS 启动文件和一份清单;只要机器上有 Node.js 就能运行,同一份.wasm跨平台通用。系统能力由 JS 适配层经 Node 的文件、网络、进程等 API 提供。这个 JS 文件是 GC 接口的宿主适配,不是底层接口的后端。 - 路二(原生路)。 发行物是原生可执行文件,用户机器上不需要 Node 或任何运行时。按“操作系统 × 指令集”各出一份,共 3 × 2 = 6 个目标,外加自有裸机。二进制里含执行器与平台后端,后端使用该平台正确的系统入口:Linux 直接发系统调用;macOS 与 Windows 没有稳定的系统调用编号,走系统库的导入表(第六节)。发行物有两种形态,出自同一份代码(第 4.4 节):原生宿主,运行时装载外部的
.wasm,也能装载符合其支持特性集的第三方标准模块;独立可执行文件,把程序封装在宿主副本里,单个文件即可运行。
默认与备用。 路二跑通之后是默认的运行方式,用户程序和我们自己的开发工具链都一样;路一是备用方案,用在路二跑通之前、没有对应原生二进制的平台,以及需要对照排查的时候。
两条路必须对同一份程序给出相同的接口语义,靠一致性验证保证(第十节)。
四、编译与程序表示
4.1 前端流水线【已实现】
编译器用豫言自己写成(自托管)。前端按文件依次做:解析、名称解析、类型检查、类型擦除、跨模块优化、闭包转换,得到“闭包形式”。这些阶段按依赖顺序并行派发。闭包转换之后,整个模块构造统一 ANF(统一的中间表示),再交给 Wasm 后端生成 Wasm 模块。
4.2 唯一的后端:Wasm
编译器只有一个后端,产出标准 Wasm。两种语言模式(第 4.3 节)共用同一套中文 Wasm 模型与二进制编码器:
| 模式 | 如何选中 | 产物 | 状态 |
|---|---|---|---|
| 高级模式(缺省) | 包描述不写编译模式;今天还须加 --target=wasmgc |
统一 ANF → 内存中的 Wasm 模块 → 直接编码为 .wasm 二进制(WasmGC),含用豫言写的垃圾回收运行时;不经 WAT 文本,不调用汇编器;要求宿主支持 GC、异常处理与尾调用;对外只有一个导入 yuyan:gc-host/v1.call |
【已实现】 |
| 底层模式 | 包描述写 「编译模式」者『底层』也。 |
闭包前的优化形式 → 只含 32 位整数的 Wasm,不用标准库与垃圾回收 | 【已实现】 |
机器码不由编译器后端产生,而由路二的提前编译(寄存器降级,第六节)从 Wasm 生成;裸机镜像就是这样得到的。
要删除的原生后端。 今天编译器的默认目标仍是 --target=native(别名 llvm、shadow):由统一 ANF 生成“直接调用”的 C 源码,再由 clang 以 -O3 -flto 编译,与预建的 C 运行时(42 个文件,约 4700 行)链接。编译器自己、豫构和各类工具目前都靠它运行。它将在 WP1 整体删除,--target 选项随之取消,编译器只出 Wasm。
要点:路一与路二用的是同一个 .wasm。同一份 .wasm 既可以交给 JS 宿主(路一),也可以交给执行器(路二)。差别只在“由谁来实现接口”,不在编译。
4.3 两种语言模式
- 语言只有一种,模式是包的属性:
高级(缺省,有垃圾回收,可用标准库)与底层(无垃圾回收,不用标准库)。同一依赖闭包内必须同一模式,豫构解析包图时检查。 - 底层模式沿用全部语法与类型检查,只是后端能接受的更少:允许整数、爻、单元、
若…则…否则、虑、对整数常量的鉴、一阶函数、自递归与尾调用(尾调用变回跳)、函数内可变格与外部调用原语;不允许元组、构造器、字符串、小数、异常、闭包、数组、引用类,越界则编译期报错,绝不悄悄退回垃圾回收。所有值统一为 32 位整数。 - 现有 14 个底层包:底层库及其示例、底层接口的 6 个规范包、执行器
客体二号、WASI库、裸机探针与样例库,以及一个 WP0 将删除的过渡包。 - 底层模式取代了此前自造的括号式“低阶语言”;不再新造语言或领域专用语言。
4.4 路二的形态与两档执行
路二的可执行文件是同一份豫言底层模式程序,编成整数 Wasm,再经寄存器降级和格式写出器得到;里面依次是:平台后端(底层接口的 45 个函数)、两个 shim、执行器(含豫言写的垃圾回收运行时)。它有两种形态:
- 原生宿主。 命令行给出
.wasm路径,运行时装载并执行。按《统一接口方案》既有决定,宿主按真实导入处理标准模块,不以豫言编译器产物为唯一合法输入;导入由宿主明确提供,否则在装载时拒绝。 - 独立可执行文件。 把
.wasm封装进宿主副本(追加为数据段),得到单个文件。这一步只搬字节,不需要编译。将来 AOT 之后,机器码也一并封装进去。
执行分两档,让路二既能尽快闭环,又最终达到原生速度:
- 甲档:解释执行。 执行器逐条解释 Wasm。这是裸机上已经验证过的形态(
客体二号),用作正确性基准。 - 乙档:提前编译(AOT)。 把 WasmGC 程序编成目标机器码,与 GC 运行时、平台后端链接成一个二进制,没有解释器。这是终局,也是路二里最大的一块工作(WP8);先做提前编译,后做即时编译。
《系统编程语法方案》把整条路分成五个阶段,当前进度如下:
| 阶段 | 内容 | 状态 |
|---|---|---|
| A 统一表示 | 中文 Wasm 模型、文本读写、二进制编解码与验证 | 【已实现】 |
| B 最小执行器 | 用豫言写的选定子集解释器 | 【已实现】(客体二号) |
| C 自有提前编译 | Wasm 编成机器码,无需外部汇编器与链接器 | 【部分实现】仅整数子集,ARM64 与 AMD64 |
| D 托管与系统能力 | 覆盖现有豫言所需的 WasmGC 等特性与宿主服务 | 【部分实现】解释执行一个子集 |
| E 独立自举 | 编译器与执行器都经 Wasm 生成,上一代 AOT 出下一代,禁用 clang、cc、as、ld 与 libc 仍能自举 | 【规划】 |
目前生成裸机镜像的生成器,仍由 C 后端构建;通过 Wasm 运行它的那条路径也仍借助 Wasmtime 宿主。所以“客体不依赖 LLVM 与 libc”不等于“整个工具链已经独立”。WP1 之后,生成器以 WasmGC 在 Node 上运行。
五、两层接口与两个 shim
5.1 豫言操作系统接口(GC 接口)
- 仓库根目录
豫言操作系统接口/,纯源级规范:只有接口包、纯声明和规范文字,。接口。豫中的函数由宿主提供,。应用接口。豫中的函数由应用提供,方向由文件后缀决定。 - 【部分实现】共 52 个能力包、267 个函数签名,按能力分五组:最初六包(基础、启动、网页服务、文件、网络、显示);浏览器页面(界面、文树、消息、事件、定时、储存、导航、环境、请求、事件源、编译、应用,及剪贴板);跨宿主工具(时间、随机数、日志、文字规整、网址、网址定位、网址查询、密码摘要、口令派生、压缩);服务端 HTTP 与凭据(入站、答复、转发、事件流、上游、授权服务、服务状态、环境文字、秘密文字);持久与编译(持久事务、告警、独占、频道,命名持久服务,关系查询与数据库,对象存储,消息队列,定时事件,邮件发送,编译运行,隔离运行,平台资料)。
- 豫言类型系统是规范,WasmGC 只是向下的投影。Wasm 层目前只有一个通用导入
yuyan:gc-host/v1.call(按需增长的外调通道,不冻结)与一个导出_start。 - 装载规则:缺少某个包、或版本与签名不符即装载失败,版本须完全相等;依赖一律必需,授权另行检查。
5.2 豫言操作系统底层接口(非 GC)
- 仓库根目录
豫言操作系统底层接口/,同样是纯规范。规范【已实现】,实现目前只有裸机后端。6 个包、45 个函数:基础 11、句柄 13、文件 9,三者为核心;网络 7、终端 2、进程 3,为可选,平台没有时返回 58(不支持)。 - 值只有 32 位整数,数据以(地址,长度)和出址传递,每个函数返回状态码;状态码取 WASI preview1 errno 的子集,编号不变。记录布局(状态块、目录项、订阅、事件)沿用 WASI。
- 句柄即能力:初始授权是启动时的预开目录,路径一律相对目录句柄,不得越出其子树,越界返回 76。
- 没有全局“当前目录”“路径规范化”“HTTP”“JSON”,这些由 shim 用豫言实现,不进后端。首版单线程,不含显示与输入,不含 HAL。
- 只有两个使用者:GC shim 与 WASI shim。应用不直接调用它。
5.3 两个 shim
| shim | 输入 | 输出 | 现状 |
|---|---|---|---|
| GC shim | 豫言 GC 程序经 yuyan:gc-host/v1.call 发出的 72 个系统原语 |
底层接口调用 | 【部分实现】原语在 客体二号/主机*.豫;控制台输出(1–4 号)已改经底层接口,其余仍直调内核 |
| WASI shim | 外来工具链(C、C++、Rust 等)产出的 WASI preview1 程序 | 底层接口调用 | 【规划】WASI库 已有,但未接入、不保证可用 |
shim 用豫言底层模式写,与执行器同一编译单元。GC 值与线性内存之间的搬运在 shim 里完成:GC 字节数组的内容本来就在执行器内存里,可直接以(地址,长度)交给底层接口。
5.4 一次调用如何穿过各层
以“打印一行”为例:
| 步骤 | 路一(Node) | 路二(Linux 原生) |
|---|---|---|
| 应用 | 调用日志或标准库打印 | 同左 |
| 适配 | 豫言写的适配包,落到宿主库的外调名 | GC shim 原语 1 |
| 宿主 | Node 适配层的 JS 调用 process.stdout.write |
底层接口 写(句柄 1, 地址, 长度, 出址) |
| 系统 | Node 与 V8 | Linux 后端发出 write 系统调用 |
六、路二:直接生成原生二进制
6.1 流水线
底层模式(或经 shim 的执行器)产出的整数 Wasm → 寄存器降级(指令选择与寄存器分配,ARM64 与 AMD64 两个后端,【已实现】用于裸机)→ 可执行文件写出器(ELF、Mach-O、PE,【规划】)→ Apple 平台的代码签名(【规划】)。全程是豫言代码,不调用任何外部工具。机器码由豫言里的字段化构造函数直接编码字节,检查寄存器号、立即数范围与位域,不写英文汇编源文件。
6.2 各平台如何进入操作系统
后端与操作系统之间只有一张入口表:这是数据,不是代码。
| 能力 | Linux(系统调用) | macOS(libSystem 导入) | Windows(kernel32 / ntdll 导入) | 裸机(内核服务) |
|---|---|---|---|---|
| 时钟 | clock_gettime | clock_gettime_nsec_np | QueryPerformanceCounter、GetSystemTimePreciseAsFileTime | 滴答与墙钟服务 |
| 随机 | getrandom | getentropy | BCryptGenRandom | 熵源未定 |
| 读写与位置 | read、write、pread64、pwrite64、lseek | 同名 POSIX 函数 | ReadFile、WriteFile、SetFilePointerEx | 控制台服务与执行器内文件系统 |
| 打开与路径 | openat2 加 RESOLVE_BENEATH、newfstatat、mkdirat、unlinkat、renameat、readlinkat、getdents64 | openat、fstatat 等 | NtCreateFile(RootDirectory)、NtQueryDirectoryFile | 执行器内文件系统 |
| 等待 | ppoll | kevent、poll | WaitForMultipleObjects | 内核时钟与邮箱 |
| 进程与管道 | clone / execve、wait4、pipe2 | posix_spawn、waitpid | CreateProcessW、CreatePipe | 执行器的复制任务与加载 |
| 退出 | exit_group | exit | ExitProcess | 退出任务服务 |
- 为什么 macOS 与 Windows 走库导入。 Apple 不保证系统调用编号稳定;Windows 的调用号随版本变化。这两个平台受支持的入口是系统库,所以写出器必须能产生 Mach-O 与 PE 的导入表;这不等于依赖 libc,导入的只是操作系统自己的入口符号。Linux 有稳定的系统调用接口,直接发即可。
- 路径编码。 接口一律 UTF-8;Windows 的路径是 UTF-16,转换在后端里做。
- 越界防护是后端里最厚的一块。 Linux 有一步到位的
RESOLVE_BENEATH;macOS 的等价标志因系统版本而异,Windows 没有,后端要逐段解析路径并拒绝越界。这里最容易出安全问题,需要专门测试。 - 线性内存。 Wasm 线性内存映射为向系统预留的一块虚拟地址区(
mmap或VirtualAlloc),增长即提交页;内存增长是语言指令,由代码生成负责,不是接口函数。 - 进程入口。 由写出器生成入口代码:Linux 从初始栈读取参数与环境,macOS 与 Windows 经系统库取得,再交给底层接口的“取参数块”“取环境块”。
6.3 GC 程序在路二上如何运行
带 GC 的程序编成 WasmGC,由执行器运行:先是解释器(正确性基准),再是提前编译(AOT,把 WasmGC 编成机器码,运行时的垃圾回收器同样用豫言底层模式写)。每个隔离执行域有独立的托管堆与根,回收在用户态进行。裸机上的 客体二号 就是这个执行器的第一版,目前只覆盖 WasmGC 的一个子集(不验证模块,不支持 SIMD)。要让编译器自己也能在路二上运行,就需要覆盖它用到的全部 WasmGC、异常与尾调用特性。AOT 与完整 WasmGC 是路二里最大的一块工作(第十二节)。
已有的原生 C 运行时(约 4700 行)是同一件事的旧实现:它有自己的垃圾回收器和系统操作,但只接受豫言源码经 C 编出的程序,不能装载任意 Wasm。它随 C 后端在 WP1 删除;路二的垃圾回收运行时用豫言底层模式重写(WP8)。
七、裸机系统:我们自己的操作系统
【已实现,仅在 QEMU 上运行,尚未上真实硬件】
- 引导。 ARM64 自造 ELF64 头,EL1 建页表后
eret进入 EL0;AMD64 走 Multiboot,进长模式后落入 Ring 3。目前只用 QEMU 直接装载内核。 - 内核。 机器字节由豫言源直接生成,不用汇编器与链接器;用户代码是 Wasm 整数核经寄存器降级得到的机器码,两个架构走同一遍历。
- 内核服务。 编号 0 至 27,共 28 项:控制台写字节与读字节;验收用的核验与完成;进入用户态;时钟滴答;任务编号;用户陷阱;任务状态与退出码;等待、终止、复制、回收任务;发送、接收消息,查询来源与等待消息;退出任务;设备配置读与寄存器读写;内存块分配与计数;搬入与搬出静态区;墙钟秒;中断查询。ARM64 用
svc,AMD64 用int 0x80。 - 内存与隔离。 2 MiB 大页,每任务独立页表,32 个 2 MiB 的动态块池;设备 DMA 依靠 SMMUv3 与 VT-d 隔离。尚未实现 W^X。
- 任务。 2 个任务槽,100 Hz 抢占轮转,带代际的任务句柄;支持复制、等待、终止、回收;任务间通信是单条 32 位邮箱加阻塞接收;故障只终止当前任务。
- 执行器
客体二号。 底层模式的豫言程序,约 1.6 万行,值全是 32 位整数;解释执行 WasmGC 子集,自实现 72 个系统原语(其中 54 号未实现),向内核导入 20 个服务(平台 15、块设备 5)。加载窗口 448 KiB,盘上程序至多 4 MiB,经复制任务原地重启,可多实例;不验证模块。 - 存储与终端。 用户态 Virtio 块设备驱动,双区追加日志的文件系统;输入环与 Ctrl-C;命令壳本身是一个普通豫言程序,支持文件操作、运行程序、重定向、管道、环境变量与脚本。
- 限制。 只有 2 个任务槽,所以一次只能有 1 个子进程;不支持 SIMD;只在 QEMU 的 TCG 单核 128 MB 配置下验证。
- 与底层接口的关系。 裸机后端
裸机底层*(45 个函数)已实现并通过验证,其中 15 个返回“不支持”;客体二号与这些模块已解耦,GC shim 对它的改接刚起步。
八、路一:JS 宿主
- 浏览器【已实现】:前端主力,20 个能力包有适配,已在 Chromium 152 上逐项验证(155 例)。没有 JSPI 的浏览器(如 Safari)只能运行只用同步能力的程序;等待网络、DOM 事件或流的界面程序还需另解异步续执行。
- Cloudflare Worker(云工)【已实现】:服务端主力,33 个能力包有适配;验证限于 Node 模拟与本地 workerd,云端环境的行为尚待验证。
- Node.js【部分实现】:目前只有编译器自举用的 V8 宿主(
宿主/节点/),没有说明文档,也没有装载核对,接口只验证了基础样例。WP1 之后到 WP8 之前,它是开发工具链的运行环境;WP8 之后工具链默认改用原生二进制,Node 保留为备用。要成为路一的桌面宿主,必须补齐文件、网络、时间、随机数、日志、环境文字、密码摘要、网址等能力包的 Node 适配、宿主支持清单、装载核对与一致性验证(第十二节)。 - 适配机制【已实现】:适配包用豫言写在宿主库之上;每个宿主附一份人工维护的“宿主支持清单”,每个应用附检查器生成的“应用要求清单”,清单含身份、依赖闭包、方向、签名和规范摘要;启动前的装载核对要求两份清单相等,并检查 Wasm 的单一导入与
_start导出。逐函数是否真正连通,靠运行验收。
九、工具链、自举与发行
9.1 包与构建【已实现】
- 一切皆包。 包身份由“所有者 + 名称”确定;包描述
名。包。豫必填名称、所有者、版本、简介、说明、类型,依赖按需,只能指向库,没有远程仓库,也没有锁文件。每个包必须带双语“包说明”,缺失则构建报错。只能导入自身与直接依赖;可执行包不可被依赖;依赖不得成环(标准库 → 构建基础/癸象工具 → 编译器核心 → 包配置 → 豫构)。 - 构建工具:现有两个,将合并为一个。 今天
豫构负责包发现、检查、构建、类型检查与自举包导出;仓库构建是早先取代根 Makefile 与运行时 Makefile 的构建程序,负责 C 运行时、编译器自举链、构建豫构自身、测试与打包;云仓另有同类的云仓构建。三者共用构建编排库做并行调度与缓存。仓库构建之所以独立,主要是为了编排 C 运行时与原生自举链这类“不是包”的步骤。WP1 删除 C 后端后,这些理由大都消失,因此在 WP1 中把它并入豫构:自举、测试、打包成为豫构的命令;仓库特有的多步编排(如官网构建、裸机验证)写成由豫构编译并运行的目标包(第十三节 D11),云仓构建照此改写。仓库里仍然没有 Makefile 和 shell 脚本。 - 缓存。 编译缓存在
.yybuild/下,随编译器可执行文件的修改时间分开;仓库构建的目标印记按输入的修改时间判定。同一缓存同一时刻只能有一个构建。
9.2 自举链【已实现】
yy_bs_stable(稳定种子)→ yy2_bs → yy3_bs → yy4_bs,yy3_bs 与 yy4_bs 逐字节相同即为定点。改动编译器可能影响生成语义时必须跑完整自举。首级必须串行,此后可并行。
今天这条链上的是 C 后端产出的原生程序,完整自举约 10 分钟,大部分时间花在 clang 上。WP1 删除 C 后端后,同一条链改为 .wasm 在 Node 上运行,以 .wasm 逐字节相同为定点。性能研究的记录里,Wasm 编译器在 Node 上冷缓存编译自己一代约 20 秒,二代与三代逐字节一致(2026-09-26 前后多次测得,未清系统页缓存)。路二的提前编译完成后,再用它把编译器编成原生二进制(WP8),此后默认用原生编译器,Node 上的 Wasm 版作备用。
9.3 种子与起步
| 起步方式 | 内容 | 起步需要 | 状态 |
|---|---|---|---|
| Wasm 工具链包 | 编译器、豫构、仓库构建三个 WasmGC 模块,加 Node 宿主与值桥。CI 每轮发布,按提交号存档;主 CI 用固定网址与 SHA-256 取用 | Node.js;目前还需要 clang,用来构建 C 运行时和原生产物 | 【已实现】 |
| Linux 可执行程序包 | 稳定编译器、稳定豫构与运行时归档 | Linux x86-64;编译新程序仍需 clang | 【已实现,WP1 删除】 |
| 可移植自举包 | 双种子 C 源码、运行时与构建脚本 | 仅需 clang | 【已实现,CI 已停发;WP1 删除】 |
| 唯一的种子 | WP1 之后只剩这份 Wasm 工具链包,在 Node 上运行;WP8 之后,Node 上的 Wasm 编译器还能直接产出路二的原生二进制,自举链可在原生二进制上继续 | 只需要 Node.js 与一份 SHA-256 固定的压缩包,不再需要 clang | 【规划】(WP1) |
Node 在这里是种子的运行环境,也是路一的宿主;它是备用方案而不是产品依赖,任何路二的二进制都不需要 Node。
9.4 发行物
| 发行物 | 内容 | 状态 |
|---|---|---|
| Wasm 工具链包 | 见上表 | 【已实现】 |
| 路一发行包 | 一份 .wasm、一个 JS 启动文件和一份清单 |
【规划】(WP9) |
| 路二发行物 | 每个“操作系统 × 指令集”一个可执行文件,共 6 个,另有裸机镜像 | 【规划】(WP3 至 WP6);裸机镜像【已实现,QEMU】 |
工具产物一律以 yy 开头或以 .exe 结尾,版本管理据此忽略。
9.5 非豫言代码盘点与去向
2026-09-28 的统计(git 跟踪文件):
| 类别 | 规模 | 去向 |
|---|---|---|
原生 C 运行时 豫言操作系统/宿主/原生/运行时/ 及其测试 |
43 个文件,约 4800 行 | WP1 删除 |
构建基础的包内 C(包上下文.c,保存编译器包上下文) |
1 个文件 | WP1 改写为豫言 |
生成 C 的后端(统一原生直生、原生链接、可移植自举包、原生根活跃分析)与 clang 调用 |
豫言写的编译器部分 | WP1 删除 |
Wasmtime 的 C 桥接(网页汇编宿主)与不入库的 yy_wasmtime_c_api |
4 个文件;仍被裸机镜像生成与部分测试使用 | WP1:使用者改走 Node,然后删除 |
为验证底层接口而建的过渡 Wasmtime C 宿主 底层原生宿主 |
2 个文件 | WP0 删除 |
WASI 示例 裸机/WASI库/示例/源码 |
22 个 C 程序 | 保留:它们是 WASI shim 的“外来程序”样本,不是我们的实现 |
| 裸机验证夹具 | 2 个 C 文件 | 保留,测试外部模块用 |
| 性能研究、安全外壳的包内 C | 12 个文件 | WP1:改写为豫言,或移出仓库;C 后端删除后它们无法再构建 |
| JavaScript | 116 个文件,约 2.2 万行,其中路径含“测试”者约 1.27 万行 | 浏览器、Worker、Node 三类宿主外壳与 VS Code 扩展必须保留;测试夹具逐步改为豫言 |
| WAT | 2 个文件,共 41 行 | 随 Wasmtime 桥接在 WP1 删除 |
十、验证体系
10.1 现有验证
| 验证 | 覆盖 | 规模 | 耗时(实测) |
|---|---|---|---|
| 裸机验证 | 内核 ABI 与执行器,QEMU 双架构 | 190 场景 × 2 = 380 项(另有外部 C 模块 4 场景) | 运行约 70 秒到 2 分钟;构建约 15 分钟(单个 59 MB 的生成 C 文件经 clang -O3 -flto) |
| 普通豫言验证 | 把 WasmGC 程序嵌入执行器,QEMU 双架构,对比串口与退出码 | 42 项 × 2 = 84 次运行 | 约 12.5 分钟,完全串行;最重的一次(AMD64 长打表)就要 60 至 85 秒 |
| 完整自举 | 三代编译器,第三、四代逐字节一致 | 3 个阶段 | 原生约 10 分钟,阶段间必须串行;Wasm 版在 Node 上约 20 秒一代 |
| 全树测试 | 各库与应用的单元测试 | CI 分 20 片 | 未在本篇测量 |
| 接口检查器与一致性验证 | 接口清单、装载核对;浏览器 155 例、云工壳 36 项加 82 项差分 | — | — |
10.2 目标:整套验证 2 分钟内完成
阻碍它的东西,按已经测得的事实排列:
- 构建太慢的根源是生成 C。 一个 59 MB 的单文件交给 clang 做全程序优化,单线程,无法并行。WP1 删除 C 后端后这一项立即消失:工具改为
.wasm在 Node 上运行,不再经过 clang。 - 运行器是串行的。 普通豫言验证的主循环对每个测试依次做:编译探针、跑 ARM64、跑 AMD64。84 次相互独立的运行在 18 核机器上只用了约 18% 的算力。
- 每个测试重复付出固定成本。 每个探针启动一个完整的
yy豫构进程(全仓包发现);每个镜像都把执行器重新降级为机器码,而不同测试之间只有嵌入的客体数据段不同。以上两点是从代码结构与总 CPU 时间(约 2434 秒,其中系统态与用户态相当)作的推断,尚未分阶段剖析,实施时先测后改。 - 关键路径有硬下限。 单个最慢的 AMD64 测试(QEMU TCG 单线程)需要约一分钟,任何并行都压不到它之下;要么缩小该测试的工作量,要么接受下限。
- 超时预算太紧。 每次 QEMU 运行限时 60 秒(1200 × 50 毫秒),机器繁忙时最慢的测试会稳定超时,而“首败即整体中止”会让整套验证看起来坏了。
- 自举链天然串行。 三个阶段互相依赖。原生版约 10 分钟,放不进 2 分钟;按 Wasm 版每代约 20 秒推算,完整自举约 1 分钟,可以放进去(待 WP1 实测,另见第十三节 D4)。
设计:验证分级。快检(目标 2 分钟内)包括类型检查、按变更范围选取的测试、并行的 QEMU 冒烟;完整验证包括自举、全量双架构与双路一致性,允许更久,但同样要求并行、缓存与可增量。
10.3 一致性验证骨架
【规划】用豫言底层模式写的接口一致性测试,每个能力一组,在 Linux、macOS、Windows 与 QEMU(ARM64、AMD64)上输出必须逐字相同;GC 接口的适配一致性用例既在路一的宿主上跑,也在路二上跑;WASI 示例的 22 个 C 程序,输出与退出码须与 Node 自带的 node:wasi 逐字一致。测试同时是接口的规范样例。
十一、现状总表
| 项目 | 状态 |
|---|---|
| 语言与编译器:自托管、自举定点、Wasm 工具链包 | 【已实现】 |
| 编译器直接写出 WasmGC 与整数 Wasm 二进制(不经 WAT) | 【已实现】 |
| 中文 Wasm IR、ARM64 与 AMD64 寄存器降级 | 【已实现】(用于裸机) |
| GC 接口:52 包规范,浏览器 20 包、云工 33 包适配 | 【部分实现】 |
| 底层接口:6 包 45 函数规范 | 【已实现】 |
| 裸机后端与裸机系统 | 【已实现】(仅 QEMU) |
| GC shim 改接底层接口 | 【部分实现】(仅控制台输出) |
| WASI shim | 【规划】(WASI库 未接入) |
| Linux / macOS / Windows 原生后端与可执行文件写出器 | 【规划】 |
| 代码签名(Apple) | 【规划】 |
| WasmGC 原生执行(AOT)与豫言写的 GC 运行时 | 【规划】 |
| Node 作为路一的桌面宿主 | 【部分实现】 |
| 单一后端:删除 C 后端、C 运行时、clang 调用与 Wasmtime 编译宿主工具 | 【规划】(WP1) |
| 单一构建工具:仓库构建与云仓构建并入豫构 | 【规划】(WP1) |
| 全系统验证 2 分钟 | 【规划】 |
今天实际能用的运行方式:
| 方式 | 说明 |
|---|---|
| 浏览器、Cloudflare Worker | 路一的两个 JS 宿主,产品级使用中 |
| 原生(C 加 clang) | 迁移期旧路径:豫言编成 C,再由 clang 编成本机程序。编译器自己、豫构和各类工具都靠它运行。WP1 删除,不属于目标矩阵 |
| Node 上的 Wasm 工具链 | 编译器、豫构、仓库构建的 WasmGC 版本,用于 CI 起步;WP1 之后是唯一的工具链。它也是路一的雏形,但尚无面向应用的适配 |
| QEMU 上的豫言裸机系统 | 路二在自有系统上的形态,双架构,已可运行命令壳和 WasmGC 子集程序 |
代码规模(2026-09-28,git 跟踪文件): 豫言约 15.9 万行,其中编译器核心 5.0 万、标准库与底层库 0.65 万、其余库 2.9 万、应用 2.0 万、裸机内核与寄存器降级 1.0 万、执行器 1.6 万、命令壳与其余 0.56 万、宿主适配包 2.0 万、接口规范 0.14 万。非豫言代码见第 9.5 节:C 与头文件 86 个约 0.94 万行、JavaScript 约 2.2 万行、WAT 41 行。JavaScript 多于 C,因为浏览器、云端与 Node 三类宿主外壳和大量测试夹具都是 JS。
十二、实施路线
下列工作包按依赖排序;规模用相对量 S / M / L / XL 表示。本路线已审定,按此一次性实施,中途不改路线;需要改动时先回到本篇。
| 工作包 | 规模 | 主要内容 | 验收 |
|---|---|---|---|
| WP0 对齐与清理 | S | 删除偏离路线的产物:Wasmtime C 宿主与 底宿主:call 包装包;回退为它加的“Wasm 导出线性内存”编译器改动;更新旧方案的状态行,修正第十四节的矛盾 |
仓库不含新增 C;完整自举定点通过 |
| WP1 单一后端与单一构建工具 | L | 编译器、豫构、仓库构建、全树测试、裸机验证、普通豫言验证等全部工具改以 WasmGC 在 Node 上运行;删除原生后端(统一原生直生、原生链接、可移植自举包、原生根活跃分析)、C 运行时与运行时支持库、--target 及其原生相关选项、Linux 可执行程序包与可移植自举包;Wasmtime 桥接与 WAT 一并删除;包内 C(构建基础、安全外壳、性能研究)改写为豫言或移出;云仓的原生单元测试改在 Node 上运行;CI 不再安装 clang;仓库构建并入豫构(自举、测试、打包成为豫构的命令,仓库特有的编排写成目标包),云仓构建照此改写 |
仓库不再有生成 C 的代码路径与 C 运行时;只剩豫构一个构建工具;在没有 clang 的机器上,从 Wasm 工具链包起步完成自举定点(.wasm 逐字节一致)与现有全部验证;公开记录各项耗时 |
| WP2 目标描述抽象 | M | 把寄存器降级里与“裸机”耦合的部分(服务号、内存布局、入口)抽成目标描述:(操作系统,指令集,可执行格式,入口绑定表,内存策略) | 裸机 380 项与 84 项验证结果不变,镜像逐字节相同 |
| WP3 Linux 最小闭环 | L | ELF64 写出器(x86-64、arm64);系统调用降级;进程入口;mmap 线性内存;退出、写、读 |
“你好”二进制的输出与退出码在 Linux x64 与 arm64 上通过;readelf 确认静态无依赖 |
| WP4 Linux 后端全量与一致性骨架 | L | 45 个函数;openat2 越界防护;一致性测试骨架 |
一致性组在 Linux 与裸机上输出逐字一致 |
| WP5 macOS | L | Mach-O 写出器(arm64、x86-64):LC_MAIN、LC_LOAD_DYLIB、绑定信息、ad-hoc 代码签名;libSystem 导入;45 个函数 |
本机 arm64 通过一致性组;x86-64 经 Rosetta 或 CI |
| WP6 Windows | L | PE32+ 写出器(x64、arm64);kernel32 / ntdll 导入表;UTF-16 路径;NtCreateFile 的 RootDirectory;45 个函数 |
CI 上 Windows x64 与 arm64 通过一致性组 |
| WP7 shim 全量改接 | L | GC shim 的文件与时钟原语适配层(开柄、读、关;错误码映射;整数纳秒转浮点);行模式的 读 改为返回含换行的字节流,使“文末”与“空行”可区分;WASI shim 接回执行器 |
现有验证在改接后全过;GC 程序在各平台原生二进制上跑通;WASI 示例 22 个逐字一致 |
| WP8 WasmGC 原生执行与独立自举 | XL | 执行器补全 WasmGC;AOT;豫言底层模式写的 GC 运行时;隔离执行域;编译器经 AOT 在路二运行,上一代出下一代 | 编译器自身的 WasmGC 模块在路二运行,输出与路一逐字一致;禁用 clang、cc、as、ld 与系统 libc,由原生二进制完成自举定点;检查实际构建调用、动态依赖与未定义符号;工具链默认改用原生二进制,Node 版保留为备用 |
| WP9 路一:Node 宿主 | L | GC 接口在 Node 上的适配、宿主支持清单、装载核对、文档;发行包(.wasm 加 JS 加清单) |
三个平台上同一份 .wasm 通过适配一致性用例 |
| WP10 验证体系 | M | 并行运行器、镜像与探针缓存、去除逐测重复编译;快检与完整验证分级;CI 增加 Windows 与 arm64 运行器 | 快检 2 分钟内;完整验证的耗时与各阶段公开记录 |
| WP11 文档与官网 | S | 本篇、两个接口规范、官网架构页与示意图同步 | 双语一致;官网可访问 |
依赖:WP0 → WP1 → WP2;WP2 之后,WP3 → WP4、WP5、WP6 可并行;WP7 依赖至少一个已完成的后端;WP8 依赖 WP3;WP9、WP10、WP11 与其余并行。WP10 应尽早开始,并在 WP1 中度量 Node 上工具链的速度。
暂不做:显示与输入(底层接口 v2)、线程、GPU、JIT、真实硬件启动、HAL。
十三、裁决事项
审定时以下各项均按建议采纳。
| 事项 | 决定 |
|---|---|
| D1 系统入口:Linux 直接发系统调用;macOS 走 libSystem 导入;Windows 走 kernel32 / ntdll 导入 | 采纳(与《底层接口方案》第十一节一致) |
| D2 原生闭环从哪个平台起步 | Linux ELF 与 macOS arm64 并行:前者格式最简,后者本机可直接测 |
| D3 GC 程序在路二上先解释还是直接 AOT | 解释器只作正确性基准,工具类程序(编译器)必须 AOT,先 AOT 后 JIT |
| D4 “2 分钟”的口径 | 快检 2 分钟;完整验证(含自举)另设更长预算,但须并行、缓存、可增量 |
| D5 Node 路的最低 Node 版本与 WasmGC 特性依赖 | 固定最低版本,并写进装载核对 |
| D6 CI 平台 | 增加 Linux arm64、macOS x64、Windows x64 与 arm64 运行器(以托管运行器实际可用为准) |
| D7 C 后端的去留与时机 | 已定:删除,编译器只保留 Wasm 一个后端。时机建议放在 WP1,而不是等提前编译完成:Wasm 版自举每代约 20 秒,比原生快一个数量级;代价是 WP8 完成前,开发工具链依赖 Node |
| D8 JavaScript 代码的去留 | 保留宿主外壳;测试夹具优先改为豫言,最终 JS 只留浏览器、Worker、Node 三类外壳 |
D9 裸机 读 在行模式下是否返回换行 |
返回,使读到 0 字节才表示文末(WP7 的一部分) |
| D10 路二发行物的主形态 | 同一份宿主程序两用:可装载外部 .wasm,也可把程序封装成独立可执行文件;发行时以独立可执行文件为主,宿主形态用于第三方模块与动态装载 |
| D11 构建工具合并后,仓库特有的构建步骤写在哪里 | 已定:合并为豫构一个工具。建议新增一种包类型“目标包”:用普通豫言依赖构建编排库声明目标,由豫构编译后在 Node 上运行;豫构自身只内置通用命令(检查、构建、类型检查、测试、自举、打包) |
十四、与旧文档的关系及已知矛盾
- 《统一接口方案》已确立“桌面双宿主”(JS 加 Node,与原生宿主),并要求原生宿主能装载第三方标准模块;本篇沿用这两点。它把原生宿主写成“由 LLVM 原生后端发展而来”,并把 Wasmtime 工具的清退列为后续事项;本篇改为:路二不经 LLVM 与 C,自己直接生成机器码与可执行文件,Wasmtime 工具在 WP1 一并删除。
- 《底层接口方案》第十一节把落地路线写成“待定,三选一,建议 A”。本篇确定为 A,并把 B(译成 C 或 LLVM IR)与 C(任一 Wasm 引擎加薄胶水)明确排除。
- 《接口总纲》的“首批目标”列写着 Node.js 与原生,实际只有浏览器与云工有适配。
- 《未决事项》里“十包”“装载未完成”的说法已过时。
客体二号/说明.汉语.md与裸机/说明.汉语.md滞后于代码:验证项数(写 41 与 378,实为 42 与 380)、导入数、控制台改接状态均已变化。- 版本要求“完整版本相等”与“只增函数”并存,目前没有兼容机制;需要在版本规则里补。
- 《设计动机》仍写编译器“以 LLVM 为基础”生成“LLVM 后端码”,与现状(C 加 clang)和本篇(只保留 Wasm 后端)都不符。
- 《统一后端迁移》描述的是以 C 为默认目标的现状,WP1 后需要改写。
- 语言仓与云仓的 AGENTS.md、仓库构建与云仓构建的说明都按“两个构建工具”书写,WP1 合并后需要同步改写。
- 《语言技术规范》固定描述提交
4cdeca11的基线;据审阅,其中仍含已删除的 LLVM、WASI 旧后端的描述,且方案里的“执行约束”尚未按原样实现(现为包描述的编译模式)。 - 《包系统》写“平台运行时仍单独由 Make 准备”,而仓库已没有 Makefile,运行时由仓库构建的目标准备。《仓库包构建》的依赖顺序写“豫言编译器”,应为“编译器核心”。
- 裸机验证的构建在部分情形下崩溃(编译器 GC 初始堆不足,需设环境变量绕过),普通豫言验证中最慢的 AMD64 测试会在忙机器上超时。
十五、术语
- GC 接口:豫言操作系统接口,带垃圾回收的应用面。
- 底层接口:豫言操作系统底层接口,不带 GC 的整数窄腰。
- shim(垫层):把一种接口翻译成另一种接口的一层代码;GC shim 与 WASI shim。
- 执行器:运行 Wasm 的程序;裸机上的
客体二号是它的第一版。 - 后端:在某个平台上实现底层接口的实现包(Linux、macOS、Windows、裸机各一份)。
- 入口表:后端与操作系统之间的数据表:系统调用号,或系统库的导入符号。
- 句柄:平台发放的非负整数,同时是能力。
- 路一 / 路二:第三节定义的两条运行路径。
延伸阅读
- 豫言操作系统接口总纲:GC 接口的 52 个能力包。
- 豫言操作系统底层接口总纲与调用约定:45 个函数、状态码与记录布局。
- 底层接口方案与统一接口方案:两份前序设计。
- 系统编程语法方案与底层模式:Wasm 统一边界与无 GC 编译模式。
- 豫言裸机说明:内核、执行器与验证。
- 豫言操作系统愿景:面向读者的愿景与路线图。