跨端轻量 JavaScript 引擎的实现与探索
一、JavaScript
1.JavaScript 语言
JavaScript 是 ECMAScript 的实现,由 ECMA 39(欧洲计算机制造商协会 39 号技术委员会)负责制定 ECMAScript 标准。
ECMAScript 发展史:
2.JavaScript 引擎
JavaScript 引擎是指用于处理以及执行 JavaScript 脚本的虚拟机。
常见的 JavaScript 引擎:
3.JavaScript 引擎工作原理
a.V8 引擎工作原理
b.Turbofan 技术实例说明
这里a
和b
可以是任意类型数据,当执行sum
函数时,Ignition
解释器会检查a
和b
的数据类型,并相应地执行加法或者连接字符串的操作。
如果 sum
函数被调用多次,每次执行时都要检查参数的数据类型是很浪费时间的。此时TurboFan
就出场了。它会分析函数的执行信息,如果以前每次调用sum
函数时传递的参数类型都是数字,那么TurboFan
就预设sum
的参数类型是数字类型,然后将其编译为机器码。
但是如果某一次的调用传入的参数不再是数字时,表示TurboFan
的假设是错误的,此时优化编译生成的机器代码就不能再使用了,于是就需要进行回退到字节码的操作。
三、QuickJS
1.QuickJS 作者简介
法布里斯·貝拉 (Fabrice Bellard)
2.QuickJS 简介
QuickJS 是一个小型的嵌入式 Javascript 引擎。 它支持 ES2023 规范,包括模块、异步生成器、代理和 BigInt。
它可以选择支持数学扩展,例如大十进制浮点数 (BigDecimal)、大二进制浮点数 (BigFloat) 和运算符重载。
•小且易于嵌入:只需几个 C 文件,无外部依赖项,一个简单的 hello world 程序的 210 KiB x86 代码。
•启动时间极短的快速解释器:在台式 PC 的单核上运行 ECMAScript 测试套件的 76000 次测试只需不到 2 分钟。 运行时实例的完整生命周期在不到 300 微秒的时间内完成。
•几乎完整的 ES2023 支持,包括模块、异步生成器和完整的附录 B 支持(旧版 Web 兼容性)。
•通过了近 100% 的 ECMAScript 测试套件测试: Test262 Report(https://test262.fyi/#)。
•可以将 Javascript 源代码编译为可执行文件,无需外部依赖。
•使用引用计数(以减少内存使用并具有确定性行为)和循环删除的垃圾收集。
•数学扩展:BigDecimal、BigFloat、运算符重载、bigint 模式、数学模式。
•用 Javascript 实现的带有上下文着色的命令行解释器。
•带有 C 库包装器的小型内置标准库。
3.QuickJS 工程简介
4.QuickJS 工作原理
QuickJS 的解释器是基于栈的。
QuickJS 的对 byte-code 会优化两次,通过一个简单例子看看 QuickJS 的字节码与优化器的输出,以及执行过程。
•第一阶段(未经过优化的字节码)
•第二阶段
•第三阶段
通过上述简单的函数调用,观察 sum 函数调用过程中栈帧的变化,通过计算可知 sum 函数最栈帧大小为两个字节
5.内存管理
QuickJS 通过引用计算来管理内存,在使用 C API 时需要根据不同 API 的说明手动增加或者减少引用计数器。
对于循环引用的对象,QuickJS 通过临时减引用保存到临时数组中的方法来判断相互引用的对象是否可以回收。
6.QuickJS 简单使用
从 github 上 clone 完最新的源码后,通过执行(macos 环境)以下代码即可在本地安装好 qjs、qjsc、qjscalc 几个命令行程序
•qjs: JavaScript 代码解释器
•qjsc: JavaScript 代码编译器
•qjscalc: 基于 QuickJS 的 REPL 计算器程序
通过使用 qjs 可以直接运行一个 JavaScript 源码,通过 qsjc 的如下命令,则可以输出一个带有 byte-code 源码的可直接运行的 C 源文件:
上面的这个 C 源文件,通过如下命令即可编译成可执行文件:
也可以直接使用如下命令,将 JavaScript 文件直接编译成可执行文件:
7.给 qjsc 添加扩展
QuickJS 只实现了最基本的 JavaScript 能力,同时 QuickJS 也可以实现能力的扩展,比如给 QuickJS 添加打开文件并读取文件内容的内容,这样在 JavaScript 代码中即可通过 js 代码打开并读取到文件内容了。
通过一个例子来看看添加扩展都需要做哪些操作:
•编写一个 C 语言的扩展模块
•Makefile 文件中添加 my_module.c 模块的编译
•在 qjsc.c 文件中注册模块
•编写一个 my_module.js 测试文件
•重新编译
最终生成的 my_module 可执行文件,通过执行 my_module 输出:
8.使用 C API
在第 5 个步骤时,生成了 add.c 文件中实际上已经给出了一个简单的使用 C API 最基本的代码。当编写一下如下的 js 源码时,会发现当前的 qjsc 编译后的可执行文件或者 qjs 执行这段 js 代码与我们的预期不符:
上面的代码并不会按预期的效果输出结果,因为 js 环境下的 loop 只执行了一次,任务队列还没有来得急执行程序就结束了,稍微改动一下让程序可以正常输出,如下:
这样的操作只适合用于测试一下功能,实际生产中使用需要一个即可以在必要的时候调用 loop 又可以做到不抢占过多的 CPU 或者只抢占较少的 CPU 时间片。
四、libuv
1.libuv 简价
libuv 是一个使用 C 语言编写的多平台支持库,专注于异步 I/O。 它主要是为 Node.js 使用而开发的,但 Luvit、Julia、uvloop 等也使用它。
功能亮点
•由 epoll、kqueue、IOCP、事件端口支持的全功能事件循环。
•异步 TCP 和 UDP 套接字
•异步 DNS 解析
•异步文件和文件系统操作
•文件系统事件
•ANSI 转义码控制的 TTY
•具有套接字共享的 IPC,使用 Unix 域套接字或命名管道 (Windows)
•子进程
•线程池
•信号处理
•高分辨率时钟
•线程和同步原语
评论