基于C实现的JavaScript引擎-QuickJS
2023-07-31 10:00:47 阿炯

QuickJS 是一个轻量且可嵌入的 JavaScript 引擎,它支持 ES2019 规范,包括 ES module、异步生成器以及 proxies。除此之外,还支持可选的数学扩展,例如大整数 (BigInt)、大浮点数 (BigFloat) 和运算符重载。采用JavaScript开发并在MIT协议下授权使用。


特点

轻量且方便嵌入:其只包含一些 C 语言文件,没有额外的依赖,运行一个简单的 hello world 只需要 190 KiB 的 x86 代码;
拥有启动时间极短的快速解释器:在单核的台式 PC 上,运行 ECMAScript 测试套件的 56000 个测试大约在 100 秒内完成。一个 runtime 实例的完整生命周期在不到 300ms 内完成;
几乎完整的 ES2019 支持,包括 ES module、异步生成器和完整的 Annex B 支持(传统的 Web 兼容性);
完全通过了 ECMAScript 测试套件的测试;
可将 JavaScript 源码编译为没有外部依赖的可执行文件;
基于引用计数的 GC(以减少内存使用并具有确定性行为);
数学扩展:BigInt、BigFloat、运算符重载、bigint mode 和 math mode;
使用 Javascript 实现的具有上下文着色功能(contextual colorization)的命令行解释器;
包含使用 C 语言库封装的轻量级内置标准库。

内部实现

1 Bytecode
编译器直接生成字节码,没有中间表示(如解析树),因此非常快速。在生成的字节码上进行了多个优化步骤。选择了基于堆栈的字节码,因为它简单且生成的代码紧凑。对于每个函数,编译时计算最大堆栈大小,因此不需要运行时堆栈溢出测试。为调试信息维护了一个单独的压缩行号表。对闭包变量的访问进行了优化,并且几乎与局部变量一样快。对严格模式下的直接eval进行了优化。

2 Executable generation

2.1 qjsc 编译器
qjsc编译器从Javascript文件生成C源代码。默认情况下,C源代码使用系统编译器(gcc或clang)进行编译。生成的C源代码包含已编译函数或模块的字节码。如果需要完整的可执行文件,它还包含一个main()函数,其中包含必要的C代码来初始化Javascript引擎,并加载和执行已编译的函数和模块。

可以将Javascript代码与C模块混合使用。为了生成更小的可执行文件,可以禁用特定的Javascript功能,特别是eval或正则表达式。代码删除依赖于系统编译器的链接时优化。

2.2 二进制 JSON
qjsc通过编译脚本或模块,然后将它们序列化为二进制格式来工作。该格式的一个子集(不包括函数或模块)可以用作二进制JSON。示例test_bjson.js展示了如何使用它。

警告:二进制JSON格式可能会在不经通知的情况下更改,因此不应将其用于存储持久数据。test_bjson.js示例仅用于测试二进制对象格式的函数。

运行时

1 Strings
字符串存储为8位或16位字符数组。因此,随机访问字符总是很快。C API提供将Javascript字符串转换为C UTF-8编码字符串的函数。最常见情况是 Javascript字符串仅包含ASCII 字符串不涉及复制。

2 Objects
对象形状(对象原型、属性名称和标志)在对象之间共享,以节省内存。优化了没有洞(除了数组末尾)的数组。TypedArray访问已优化。

3 Atoms
对象属性名称和一些字符串被存储为原子(唯一字符串),以节省内存并允许快速比较。原子表示为32位整数。原子范围的一半保留给从 0 到 2^{31}-1 的立即整数字面值。

4 Numbers
数字可以表示为32位有符号整数或64位IEEE-754浮点数值。大多数操作都针对32位整数情况有快速路径。

5 垃圾回收
引用计数用于自动和准确地释放对象。当分配的内存变得过大时,会进行单独的循环移除操作。循环移除算法仅使用引用计数和对象内容,因此在C代码中不需要显式操作垃圾收集根。

6 JSValue
JSValue是一个Javascript值,可以是原始类型(例如Number、String等)或对象。在32位版本中,使用NaN装箱来存储64位浮点数。表示形式经过优化,可以高效地测试32位整数和引用计数值。

在64位代码中,JSValue的大小为128位,并且不使用NaN装箱。原因是在64位代码中,内存使用不那么关键。在两种情况下(32位或64位),JSValue恰好适应两个CPU寄存器,因此可以通过C函数高效地返回。

7 函数调用
引擎已经过优化,因此函数调用很快。系统堆栈包含Javascript参数和局部变量。

RegExp

开发了一个特定的正则表达式引擎。它既小又高效,并支持所有ES2019功能,包括Unicode属性。作为Javascript编译器,它直接生成没有解析树的字节码。使用显式堆栈的回溯使得系统堆栈上没有递归。简单的量化器经过专门优化,以避免递归。来自具有空项的量化器的无限递归被避免。完整的正则表达式文件库的权重约为15 KiB(x86代码),不包括Unicode库。

Unicode

开发了一个特定的Unicode库,因此不依赖于外部大型Unicode库,例如ICU。压缩所有Unicode表,同时保持合理的访问速度。该库支持大小写转换,Unicode规范化,Unicode脚本查询,Unicode常规类别查询和所有Unicode二进制属性。完整的Unicode库大约重量为45 KiB(x86代码)。

BigInt 和 BigFloat

BigInt 和 BigFloat 是用libbf 库 libbf 库实现的4。它大概有60 KiB (x86 代码) 并提供任意精度的IEEE 754 浮点运算和具有精确舍入的超越函数。

其作者是 Fabrice Bellard,知名开源项目 FFMPEG 和 QEMU 创建人。

最新版本:


官方站点:https://bellard.org/quickjs/

中文站点:https://github.com/quickjs-zh/