基于C实现的JavaScript引擎-QuickJS


QuickJS 是一个轻量且可嵌入的 JavaScript 引擎,它支持 ES2019 规范,包括 ES module、异步生成器以及 proxies。除此之外,还支持可选的数学扩展,例如大整数 (BigInt)、大浮点数 (BigFloat) 和运算符重载。采用C语言编写开发并在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 语言库封装的轻量级内置标准库。
功能
小而易于嵌入:引擎由几个C文件组成,并且没有任何外部依赖性。
快速解释器:解释器通过在100秒内从ECMAScript Test Suite1运行56,000次测试,并且在单核CPU上运行,显示出令人印象深刻的速度。运行时实例在不到300微秒的时间内完成其整个过程。
支持ES2019:几乎囊括全部对ES2019规范的支持,包括模块、异步生成器和完整的附件B支持(传统Web兼容性)。目前它并不支持逻辑子域和尾部调用。
没有外部依赖:它可以在没有任何外部支持的情况下将JavaScript源代码编译为可执行文件。
命令行解释器:命令行解释器带有在Javascript中实现语境着色并完善的功能。
垃圾收集:它使用引用计数和循环删除来自动和确定地释放对象。这减少了内存使用并确保了JavaScript引擎的确定性行为。
数学扩展:可以在'qjsbn'版本中找到所有数学扩展,它们与标准Javascript完全向下兼容。它支持大整数(BigInt)、大浮点数(BigFloat)、运算符重载,同时也附带'bigint'和'math'模式。
内部实现
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 创建人。
最新版本:250426
移除了大数扩展和 qjscalc。
针对小数字优化了新的 BigInt 实现。
添加了 WeakRef、FinalizationRegistry,并将符号作为弱引用。
添加了内置的 float64 打印和解析函数,以提高准确性。
重复字符串拼接速度更快。
qjs:默认情况下,未处理的 Promise 拒绝是致命错误。
在调试信息中添加了列号。
移除了 “use strip” 扩展。
qjs:添加了 -s 和 --strip-source 选项。
qjsc:添加了 -s 和 --keep-source 选项。
添加了 JS_GetAnyOpaque() 函数。
在 JSClassExoticMethods 中为奇异对象添加了更多回调函数。
修复了一些杂项错误。
官方站点:https://bellard.org/quickjs/
中文站点:https://github.com/quickjs-zh/
特点
轻量且方便嵌入:其只包含一些 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 语言库封装的轻量级内置标准库。
功能
小而易于嵌入:引擎由几个C文件组成,并且没有任何外部依赖性。
快速解释器:解释器通过在100秒内从ECMAScript Test Suite1运行56,000次测试,并且在单核CPU上运行,显示出令人印象深刻的速度。运行时实例在不到300微秒的时间内完成其整个过程。
支持ES2019:几乎囊括全部对ES2019规范的支持,包括模块、异步生成器和完整的附件B支持(传统Web兼容性)。目前它并不支持逻辑子域和尾部调用。
没有外部依赖:它可以在没有任何外部支持的情况下将JavaScript源代码编译为可执行文件。
命令行解释器:命令行解释器带有在Javascript中实现语境着色并完善的功能。
垃圾收集:它使用引用计数和循环删除来自动和确定地释放对象。这减少了内存使用并确保了JavaScript引擎的确定性行为。
数学扩展:可以在'qjsbn'版本中找到所有数学扩展,它们与标准Javascript完全向下兼容。它支持大整数(BigInt)、大浮点数(BigFloat)、运算符重载,同时也附带'bigint'和'math'模式。
内部实现
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 创建人。
最新版本:250426
移除了大数扩展和 qjscalc。
针对小数字优化了新的 BigInt 实现。
添加了 WeakRef、FinalizationRegistry,并将符号作为弱引用。
添加了内置的 float64 打印和解析函数,以提高准确性。
重复字符串拼接速度更快。
qjs:默认情况下,未处理的 Promise 拒绝是致命错误。
在调试信息中添加了列号。
移除了 “use strip” 扩展。
qjs:添加了 -s 和 --strip-source 选项。
qjsc:添加了 -s 和 --keep-source 选项。
添加了 JS_GetAnyOpaque() 函数。
在 JSClassExoticMethods 中为奇异对象添加了更多回调函数。
修复了一些杂项错误。
官方站点:https://bellard.org/quickjs/
中文站点:https://github.com/quickjs-zh/