编程 Brimstone JavaScript Engine 深度解析:Rust 能重塑 JS 引擎吗?

2026-07-25 20:14:50 +0800 CST views 14

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 的词法分析有两个关键挑战:

  1. Unicode 处理:ES2020 引入了 BigInt、Optional Chaining 等新语法,Lexer 需要正确识别所有 Unicode 字符作为标识符
  2. 模板字符串`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.pushself.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 需要:

  1. Write Barrier:每次对象字段写入时记录新老关系
  2. 新生代空间:专门存放新分配对象
  3. 晋升策略:多次回收仍然存活的对象进入老生代

在 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 的工程对比

维度BrimstoneV8
核心语言RustC++
编译器架构Bytecode + InterpreterBytecode(Ignition) + JIT(TurboFan/Maglev)
GC 策略Mark-Sweep(全量暂停)Generational + Incremental + Concurrent
代码规模~数万行~180万行
开发团队独立开发者Google 数百人团队
生产就绪
可读性高(适合学习)低(高度优化)
Unicode 支持ICU 完整支持ICU + 自有实现
ES2026 支持97%+ test262100%

性能差距:Brimstone 的纯解释执行比 V8 的 JIT 执行慢 5-20 倍是正常现象。这不是 Brimstone 的失败,而是技术路线的必然。V8 的 JIT 编译器需要数千人年的工程投入,Brimstone 的定位从来不是"更快的 V8"。


七、为什么这个项目值得关注

7.1 对 Rust 生态的意义

Brimstone 证明了 Rust 可以用来构建语义丰富、规格复杂的运行时。它的代码组织方式、GC 与 Rust 类型的边界处理,对其他 Rust 运行时光项目(如 wasmiRune)有重要参考价值。

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 当前局限

  1. 性能瓶颈:纯解释器的性能天花板很低,无法用于对性能有要求的场景
  2. 没有 JIT:JIT 编译需要大量工程投入,短期不现实
  3. 生态缺失:没有配套的 DOM API、浏览器 API,无法在浏览器中使用
  4. 调试工具:缺少 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 的霸主地位,而在于:

  1. 用 Rust 完整实现了一个工业级语言规范:97%+ 的 test262 覆盖率证明了 Rust 在复杂规格实现上的可行性
  2. 提供了一个可读的 JS 引擎学习样本:代码质量高、模块边界清晰
  3. 探索了 Rust 与 GC 共存的技术路径:证明了 Rust 的所有权系统可以在 GC 管理的运行时中正常工作

对于普通开发者,Brimstone 是一扇窗——透过它,你可以看到 JavaScript 从源码到执行的全过程,理解 V8 等商业引擎背后的设计权衡。

对于 Rust 开发者,Brimstone 是一份答卷——回答了"Rust 能不能写复杂运行时"这个问题。

对于整个行业,Brimstone 是一个信号——在 AI 辅助编程时代,像 JS 引擎这样过去需要数百人团队才能构建的系统,正变得可以被更小的团队甚至个人开发者触及。

GitHub 地址github.com/Hans-Halverson/brimstone


本文基于 Brimstone 仓库截至 2026 年 7 月的公开信息撰写。部分内部实现细节基于源码结构的合理推断,如有出入欢迎指正。

推荐文章

liunx服务器监控workerman进程守护
2024-11-18 13:28:44 +0800 CST
Python实现Zip文件的暴力破解
2024-11-19 03:48:35 +0800 CST
Go 接口:从入门到精通
2024-11-18 07:10:00 +0800 CST
Go配置镜像源代理
2024-11-19 09:10:35 +0800 CST
Vue中的表单处理有哪几种方式?
2024-11-18 01:32:42 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
程序员茄子在线接单