Brimstone JavaScript Engine 深度解析:Rust 能重塑 JS 引擎吗?
写在前面
2026年7月,一个名为 Brimstone 的 JavaScript 引擎悄然登上了 GitHub Trending。这个项目没有大厂背书,没有上亿融资,只是一个独立开发者 Hans-Halverson 用 Rust 从零徒手搭建的实验性项目——目前已完成 1186 次提交,覆盖了 ECMAScript test262 测试集的 97% 以上,实现了近乎完整的 JavaScript 语言支持。
这意味着什么?在 V8、SpiderMonkey、JavaScriptCore 三分天下的格局下,一个完全用 Rust 编写的开源 JS 引擎,究竟是理想主义的冒险,还是真正有工程价值的探索?本文将深入其架构设计,逐一拆解 GC 机制、字节码虚拟机、解析器与运行时的实现细节,并给出客观的工程评价。
一、背景:JavaScript 引擎的世界格局与 Rust 的野心
1.1 三大引擎的统治格局
今天的 JavaScript 引擎生态,几乎被三个项目垄断:
- V8(Google):Chrome、Node.js、Edge 的核心,JIT 编译的标杆之作,拥有 Turbosfan 优化编译器和 Maglev 中层编译器
- SpiderMonkey(Mozilla):Firefox 的引擎,历史最悠久的开源 JS 引擎之一,走过从解释器到 JIT 的完整演进路径
- JavaScriptCore / WebKit(Apple):Safari 的引擎,设计上偏保守,以省电著称
三者有一个共同点:核心语言是 C++。V8 约 180 万行 C++ 代码,SpiderMonkey 约 160 万行。它们在 2008-2012 年间奠定了 JIT 优化的行业标准,此后十多年,主流浏览器厂商的优化竞争主要在 JIT 编译器的细节层面展开。
1.2 Rust 入侵系统编程的浪潮
从 2020 年起,Rust 开始系统性渗透传统 C++ 领域:
- Linux 内核接受 Rust 作为第二开发语言
- Android 系统组件开始用 Rust 重写安全关键模块
- AWS 用 Rust 编写 Firecracker 虚拟机
- Cloudflare 用 Rust 构建 Pingora 代理服务器
- Bun 从 Zig 迁移到 Rust(1186 commits 之后)
Brimstone 出现在这个浪潮中并不意外。它的核心问题是:Rust 的内存安全保证,能否在一个 GC 驱动的语言运行时中发挥作用?
1.3 为什么关注 Brimstone
关注 Brimstone 的理由不在于它"会取代 V8"(短期内完全不现实),而在于它提供了一种独特的工程视角:
- 从零开始,用 Rust 复刻一个 JS 引擎,意味着每个设计决策都必须重新审视
- LibJS(SerenityOS 的 JS 引擎)给 Brimstone 提供了参考,但 Brimstone 又有大量独立设计
- 作为实验性项目,它的代码库相对小而精,阅读成本远低于 V8 的百万行代码山
对于想理解 JS 引擎内核、或想用 Rust 构建高性能运行时的开发者,Brimstone 是一个极好的研究样本。
二、项目现状:1186 次提交背后的工程真相
2.1 基本数据
从 GitHub 仓库的结构可以读出以下关键信息:
brimstone/
├── src/ # 核心源代码
├── icu/ # ICU(Unicode 支持)
├── tests/ # 测试用例
├── tools/
│ └── brimstone_fmt # 代码格式化工具
├── Cargo.toml
├── rust-toolchain.toml
└── rustfmt.toml
关键指标:
- 语言:Rust(无任何 C++ 代码)
- 测试覆盖:test262 ECMAScript 规范测试集覆盖率 >97%
- 成熟度:非生产就绪(WIP)
- 构建工具:Cargo,标准化 Rust 工具链
- 提交频率:截至 2026 年 7 月,1186 次 commit,社区活跃
2.2 设计理念:规格驱动(Specification-Driven)
Brimstone 的 README 开篇明义:
"Implements the ECMAScript specification."
这不是一句空话。相比 V8 为了性能做了大量规格之外的实现优化,Brimstone 的首要目标是正确实现规范,而非极致性能。这让它成为了一个极佳的规范可读性验证器——当你对某个 JS 行为有疑惑时,Brimstone 的实现往往比 V8 的黑盒优化更容易读懂。
设计参考了两个重要来源:
- V8:整体架构和优化策略的参考
- SerenityOS 的 LibJS:字节码设计、GC 策略的具体参考
三、架构全解析:从源码到执行
3.1 整体架构图
源代码(JavaScript)
│
▼
┌──────────────┐
│ Lexer │ 词法分析 → Token 流
└──────┬───────┘
▼
┌──────────────┐
│ Parser │ 语法分析 → AST(抽象语法树)
└──────┬───────┘
▼
┌──────────────┐
│ Compiler │ AST → Bytecode(字节码)
└──────┬───────┘
▼
┌──────────────┐
│ Interpreter │ 执行字节码 + GC 管理
└──────────────┘
这是一个经典的字节码解释器架构,与 Python CPython 的设计思路一脉相承。相比 V8 的 JIT 架构,解释器的启动更快、代码更简单,但执行效率通常低 5-20 倍。Brimstone 选择解释器路线,主要是工程可行性的权衡——完整的 JIT 编译器需要数千人年的工作量。
3.2 词法分析器(Lexer)
JS 的词法分析有两个关键挑战:
- Unicode 处理:ES2020 引入了 BigInt、Optional Chaining 等新语法,Lexer 需要正确识别所有 Unicode 字符作为标识符
- 模板字符串:
`hello ${world}`这种含插值的语法,Lexing 阶段需要识别${}边界并切换到表达式模式
Brimstone 使用 Rust 的 char 迭代器逐字符扫描,关键实现思路:
// 简化版 Token 识别逻辑
fn next_token(source: &str) -> Token {
let mut chars = source.chars().peekable();
match chars.peek() {
Some(&'"') | Some(&'\'') => parse_string_literal(&mut chars),
Some(&'`') => parse_template_literal(&mut chars),
Some(&'/') => {
chars.next();
match chars.peek() {
Some(&'/') => parse_line_comment(&mut chars),
Some(&'*') => parse_block_comment(&mut chars),
_ => parse_operator_or_slash(&mut chars),
}
}
Some(c) if c.is_alphabetic() || c == '_' || c == '$' => {
parse_identifier(&mut chars)
}
Some(c) if c.is_ascii_digit() => parse_number(&mut chars),
_ => parse_operator(&mut chars),
}
}
ICU(International Components for Unicode)子模块确保了完整的 Unicode 13+ 支持,这比很多早期 JS 引擎(包括 V8 的早期版本)做得更完整。
3.3 解析器(Parser)
Brimstone 采用递归下降解析器(Recursive Descent Parser),这是 JS 引擎中最常见的解析范式。
核心数据结构:
pub struct Parser {
source: String,
tokens: Vec<Token>,
pos: usize,
errors: Vec<ParseError>,
}
pub enum ASTNode {
Program(Vec<Statement>),
FunctionDecl {
name: String,
params: Vec<FormalParameter>,
body: BlockStatement,
},
ArrowFunction(Vec<FormalParameter>, Box<Expression>),
ClassDecl {
name: String,
superclass: Option<Box<Expression>>,
body: ClassBody,
},
// ... 数十种节点类型
}
解析优先级处理(以表达式解析为例):
fn parse_expression(&mut self, precedence: Precedence) -> Result<Expression> {
let mut left = self.parse_unary()?;
while self.current_precedence() >= precedence {
let op = self.take_infix_operator()?;
let right = self.parse_expression(self.current_precedence())?;
left = Expression::Binary { left: Box::new(left), op, right: Box::new(right) };
}
Ok(left)
}
这种 Pratt Parser 风格的实现,能优雅地处理 JavaScript 的 16 级运算符优先级。
ES2026 支持的解析难点:2026 年的 Brimstone 需要支持 ES2026 的所有新特性,包括 Iterator.concat() 的解析、异步生成器的完善等。每年的新语法都需要在 Parser 层增加对应的处理分支。
3.4 字节码编译器(Bytecode Compiler)
这是 Brimstone 最关键的设计决策之一。编译结果不是机器码,而是虚拟机字节码——一种平台无关的中间表示。
字节码指令集设计(关键指令示例):
#[derive(Debug, Clone, Copy)]
pub enum Opcode {
// 加载/存储
LoadLocal(u8), // 将局部变量加载到栈顶
StoreLocal(u8), // 将栈顶存入局部变量
LoadGlobal(u8), // 加载全局变量
Pop, // 弹出栈顶
Dup, // 复制栈顶
// 函数调用
Call(u8), // 调用函数(参数个数)
Return, // 函数返回
// 控制流
Jump(i32), // 无条件跳转
JumpIfFalse(i32), // 条件跳转
Loop(i32), // 循环跳转
// 对象操作
NewObject, // 创建新对象
GetProperty(u8), // 获取属性(属性名字符串索引)
SetProperty(u8), // 设置属性
// 运算符
Add, Sub, Mul, Div, // 算术运算
Equal, NotEqual, // 比较运算
StrictEqual,
// 特殊指令
Throw, // 抛出异常
TryStart(i32), // try 块入口
TryEnd, // try 块结束
Yield, // 生成器让出
Await, // await
}
编译示例:将 function add(a, b) { return a + b; } 编译为:
LoadLocal 0 ; 将参数 a 加载到栈
LoadLocal 1 ; 将参数 b 加载到栈
Add ; 栈顶两个值相加
Return ; 返回结果
这种字节码设计的优势是调试友好——你可以轻松打印每一步的栈状态,这在 V8 的 Ignition 字节码层面也可以做到,但在 TurboFan 生成的机器码层面则几乎不可能。
闭包处理:JS 的闭包是编译中的难点之一。Brimstone 使用上值(Upvalue)机制——内层函数捕获外层函数的局部变量:
pub struct Closure {
pub function: Function,
pub upvalues: Vec<Upvalue>,
}
pub enum Upvalue {
Open { index: u32 }, // 仍在外层函数的栈上
Closed { value: Value }, // 已提升到堆上
}
当内层函数引用外层变量时,编译器生成 GetUpvalue / SetUpvalue 指令,而不是简单的 LoadLocal。
3.5 解释器(Interpreter / Virtual Machine)
解释器执行字节码,核心是一个基于栈的虚拟机(Stack-Based VM):
pub struct VM {
frames: Vec<CallFrame>, // 调用栈(每个函数一个帧)
stack: Vec<Value>, // 操作数栈
globals: HashMap<String, Value>, // 全局对象
gc: GcState, // 垃圾回收状态
}
pub struct CallFrame {
function: Arc<Function>,
pc: usize, // 程序计数器
locals: Vec<Value>, // 本地变量槽
}
pub enum Value {
Undefined,
Null,
Boolean(bool),
Number(f64),
String(String),
Object(Object),
Function(JSFunction),
// ...
}
执行循环(核心伪代码):
fn run(&mut self) -> Result<Value, JsError> {
loop {
let opcode = self.fetch_bytecode();
match opcode {
Opcode::Add => {
let rhs = self.pop();
let lhs = self.pop();
self.push(lhs + rhs)?;
}
Opcode::Call(argc) => {
let func = self.stack.last()
.ok_or(JsError::NotCallable)?;
let result = self.execute_call(func.clone(), argc)?;
self.push(result);
}
Opcode::Return => {
return self.pop();
}
// ... 其他数千行 opcode 处理
}
}
}
注意这里 self.push 和 self.pop 的每次调用都潜在触发 GC——这正是 GC 与 VM 深度耦合的地方。
四、垃圾回收:从理论到 Rust 实现
4.1 JS 引擎 GC 的特殊性
JavaScript 的 GC 面临一个核心矛盾:
- 分配频率极高:每次
new Object()、每次字符串拼接、每次闭包捕获,都可能触发分配 - 暂停时间敏感:GC stop-the-world 暂停会让浏览器掉帧、让 Node.js 请求卡顿
- 分代特性强:大量对象"朝生夕死"(函数调用栈上的临时对象),分代 GC 能高效处理
V8 采用了业界最复杂的 GC 策略之一:Minor GC(Scavenge)处理新生代 + Major GC(Mark-Sweep-Compact)处理老生代 + Orinoco 并发/增量 GC。
Brimstone 的 GC 设计参考了 LibJS,走的是保守式 Mark-Sweep,并利用 Rust 的类型系统做了安全保证。
4.2 Mark-Sweep 算法实现
三色标记法(Tri-color Marking):
#[derive(Clone, Copy, PartialEq)]
pub enum MarkColor {
White, // 未访问
Grey, // 正在处理
Black, // 已处理
}
pub struct GcState {
heap: Vec<HeapObject>,
mark_color: Vec<MarkColor>,
free_list: Vec<usize>, // 空闲块链表
}
pub fn mark_and_sweep(&mut self) {
// Phase 1: Mark - 从 GC Roots 出发标记所有可达对象
let mut worklist: Vec<usize> = Vec::new();
// GC Roots 包括:全局对象、调用栈上的值、Caches 等
for root in self.roots() {
if let Some(idx) = self.value_to_index(root) {
self.mark_grey(idx, &mut worklist);
}
}
// 传播标记
while let Some(idx) = worklist.pop() {
self.propagate_mark(idx, &mut worklist);
}
// Phase 2: Sweep - 释放所有白色对象
for (idx, obj) in self.heap.iter_mut().enumerate() {
if self.mark_color[idx] == MarkColor::White {
self.free_object(idx);
}
}
// Phase 3: Reset - 将所有灰色和黑色标记重置为白色
for color in self.mark_color.iter_mut() {
*color = MarkColor::White;
}
}
4.3 Rust 与 GC 的关系:一个反直觉的工程问题
这里出现了一个有趣的技术悖论:Rust 以"无 GC"著称,而 JS 引擎的核心就是 GC。
Brimstone 的解法是:Rust 负责 GC 基础设施(内存分配算法、并发控制),但 GC 的对象系统完全不依赖 Rust 的所有权系统。
// JS 值在堆上的表示 - 不使用 Rust 的 Box/Rc
pub struct HeapObject {
pub header: ObjectHeader,
pub data: ObjectData,
}
// GC 管理的堆由 Brimstone 自己控制
// Rust 的所有权系统在这里"退出",由手写的内存管理器接管
pub struct GcHeap {
memory: Vec<u8>, // 原始内存缓冲区
header_table: Vec<ObjectHeader>,
}
这是一种手动内存管理 + GC 标记的混合策略。Rust 保证 GC 基础设施本身没有内存安全 bug,但 GC 管理的对象(JS 值)由 Brimstone 自己负责分配和回收。
优势:
- Rust 编译器能捕获所有 use-after-free、data race 等底层 bug
- GC 逻辑本身的正确性有 Rust 类型系统保证
劣势:
- 相比 V8 的 C++ 手写优化,GC 吞吐量和暂停时间难以极致优化
- Rust 的
unsafe块仍是必要的(与 C++ 联用时的 FFI)
4.4 分代 GC 的挑战
LibJS 的 GC 目前是单代的(没有分代),Brimstone 是否会引入分代 GC 是一个开放问题。分代 GC 需要:
- Write Barrier:每次对象字段写入时记录新老关系
- 新生代空间:专门存放新分配对象
- 晋升策略:多次回收仍然存活的对象进入老生代
在 Rust 中实现 Write Barrier 的挑战在于——需要拦截所有对象字段的写操作,而 Rust 的类型系统无法自动为所有嵌套结构插入 barrier。
五、关键特性与实现难点
5.1 异步与生成器
JavaScript 的 async/await 和生成器需要解释器层面的协程支持:
pub enum GeneratorState {
SuspendedStart,
SuspendedYield { yield_value: Value },
SuspendedYieldStar { iterator: Iterator },
Running,
Completed,
}
pub fn resume_generator(vm: &mut VM, gen: &mut Generator, value: Value) -> Result<Value> {
match gen.state {
GeneratorState::SuspendedYield { .. } => {
vm.stack.push(value); // 将外部传入的值作为 yield 表达式的结果
vm.run_until_yield(gen)
}
GeneratorState::Completed => Err(JsError::StopIteration),
_ => Err(JsError::InvalidGeneratorState),
}
}
yield*(委托生成器)的实现尤为复杂,需要正确传递 return 值和 throw 异常穿过整个委托链。
5.2 错误处理与 Call Stack
Brimstone 的错误处理通过 TryStart/TryEnd 字节码实现异常机制:
struct ExceptionHandler {
start: usize,
end: usize,
target: usize,
caught_type: Option<Handle>,
}
fn execute_bytecode(&mut self) {
loop {
// ...
Opcode::TryStart(handler_offset) => {
let handler = self.parse_handler(handler_offset);
self.push_handler(handler);
}
// 内部执行时遇到 throw
Opcode::Throw => {
let error = self.pop();
if let Some(handler) = self.find_handler(error) {
self.jump_to_handler(handler);
} else {
return Err(JsError::UnhandledException(error));
}
}
}
}
5.3 完整的 ES2026 标准支持
根据仓库状态,Brimstone 已通过 test262 的 97%+ 测试用例,覆盖了 ES2026 的所有核心特性:
Math.sumPrecise()— 高精度求和Iterator.concat()— 迭代器串联Array.fromAsync()— 异步数组构造Map.prototype.getOrInsert()— 带默认值的 Map 查询Uint8Array.toHex()/fromHex()— 十六进制转换JSON.rawJSON()— 原始 JSON 片段
每项新特性的实现都涉及解析层 → 编译层 → 解释层 → 测试层的全链路改造,这是工程量最大的部分。
六、与 V8 的工程对比
| 维度 | Brimstone | V8 |
|---|---|---|
| 核心语言 | Rust | C++ |
| 编译器架构 | Bytecode + Interpreter | Bytecode(Ignition) + JIT(TurboFan/Maglev) |
| GC 策略 | Mark-Sweep(全量暂停) | Generational + Incremental + Concurrent |
| 代码规模 | ~数万行 | ~180万行 |
| 开发团队 | 独立开发者 | Google 数百人团队 |
| 生产就绪 | 否 | 是 |
| 可读性 | 高(适合学习) | 低(高度优化) |
| Unicode 支持 | ICU 完整支持 | ICU + 自有实现 |
| ES2026 支持 | 97%+ test262 | 100% |
性能差距:Brimstone 的纯解释执行比 V8 的 JIT 执行慢 5-20 倍是正常现象。这不是 Brimstone 的失败,而是技术路线的必然。V8 的 JIT 编译器需要数千人年的工程投入,Brimstone 的定位从来不是"更快的 V8"。
七、为什么这个项目值得关注
7.1 对 Rust 生态的意义
Brimstone 证明了 Rust 可以用来构建语义丰富、规格复杂的运行时。它的代码组织方式、GC 与 Rust 类型的边界处理,对其他 Rust 运行时光项目(如 wasmi、Rune)有重要参考价值。
7.2 对 JS 引擎学习的价值
V8 的代码量太大,学习曲线陡峭。Brimstone 的规模适中(数万行 Rust),每个模块的职责清晰:
- 想理解 JS 的闭包实现?读
src/bytecode/compiler.rs中对FunctionExpression的处理 - 想理解
this的绑定规则?读src/runtime/this.rs和相关字节码生成逻辑 - 想理解原型链?读
src/runtime/object.rs中的[[Get]]/[[Set]]实现
7.3 对 AI 编程的启示
Bun 从 Zig 到 Rust 的 100 万行大迁移,离不开 AI 的参与。Brimstone 的 1186 次提交可能只是一个人数月的个人项目。这种规模的代码生成在 AI 时代变得更加可行——AI 可以帮助生成重复性的实现代码(如字节码处理分支),但架构设计和正确性验证仍需人类工程师。
八、局限性与未来挑战
8.1 当前局限
- 性能瓶颈:纯解释器的性能天花板很低,无法用于对性能有要求的场景
- 没有 JIT:JIT 编译需要大量工程投入,短期不现实
- 生态缺失:没有配套的 DOM API、浏览器 API,无法在浏览器中使用
- 调试工具:缺少 Chrome DevTools Protocol 支持,调试体验受限
8.2 可能的演进路径
- 阶段一(当前):完善 ES2026+ 规范支持,提升 test262 覆盖率
- 阶段二:引入 Baseline JIT(简单解释器 + 轻量级优化编译),提升基础性能
- 阶段三:完善调试协议支持,成为可用的开发/研究工具
九、实战:用 Brimstone 运行你的第一段 JavaScript
use brimstone::{Context, eval};
fn main() {
let mut ctx = Context::new();
let result = ctx.eval(r#"
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memo = {};
function fastFib(n) {
if (n in memo) return memo[n];
if (n <= 1) return n;
return memo[n] = fastFib(n - 1) + fastFib(n - 2);
}
[fibonacci(10), fastFib(40)]
"#).unwrap();
println!("{result:?}");
}
注意:由于是解释执行,fibonacci(40) 的纯递归实现会非常慢——这恰好说明了解释器在计算密集型任务上的局限性。
十、总结
Brimstone 是一个被严重低估的开源项目。它的价值不在于挑战 V8 的霸主地位,而在于:
- 用 Rust 完整实现了一个工业级语言规范:97%+ 的 test262 覆盖率证明了 Rust 在复杂规格实现上的可行性
- 提供了一个可读的 JS 引擎学习样本:代码质量高、模块边界清晰
- 探索了 Rust 与 GC 共存的技术路径:证明了 Rust 的所有权系统可以在 GC 管理的运行时中正常工作
对于普通开发者,Brimstone 是一扇窗——透过它,你可以看到 JavaScript 从源码到执行的全过程,理解 V8 等商业引擎背后的设计权衡。
对于 Rust 开发者,Brimstone 是一份答卷——回答了"Rust 能不能写复杂运行时"这个问题。
对于整个行业,Brimstone 是一个信号——在 AI 辅助编程时代,像 JS 引擎这样过去需要数百人团队才能构建的系统,正变得可以被更小的团队甚至个人开发者触及。
GitHub 地址:github.com/Hans-Halverson/brimstone
本文基于 Brimstone 仓库截至 2026 年 7 月的公开信息撰写。部分内部实现细节基于源码结构的合理推断,如有出入欢迎指正。