QuickJS是一个小型并且可嵌入的Javascript引擎,它支持ES2020规范,包括模块,异步生成器和代理器。
quickjs 是由 Fabrice Bellard
发布的一款小而美
的js引擎,是一款学习javascript实现原理性价比较高
的引擎,本系列介绍js规范在quickjs中是如何实现的,包括变量,对象,函数,原型,闭包,作用域,垃圾回收
等内容。
为什么要自己学习quickjs引擎?学习的过程其实是一个自我否定
和肯定的过程;在初学时往往道听途说,而不知其所以然
;当要进入授业阶段
时,需要本着严谨求实,不误人子弟
的态度,必须要从根源上明白WWW;从javascript引擎角度来说,quickjs 只是一个起点
,然后慢慢过渡到v8
这个庞然大物上。
绕不开的GDB
无论是学习quickjs还是v8,都必须学会基本的搞gdb命令,所以gdb是入门的第一课(no vscode, no xcode)。
- 编译quickjs
quickjs 是纯c编写的,编译起来相对简单,我们只需要关闭gcc的优化选项,搜索 Makefile
中的 CFLAGS_OPT
, 将 O2
修改为O0
,然后在根目录 make 即可
$ make
- 进入 gdb
make 编译完毕后会产生一个 qjs 文件,准备测试的javascript代码,执行
gdb --args ./qjs path-of-javascript
- 几个gdb指令
常用的gdb命令: b-断点,r-启动,c-继续,s-进入;p-打印,x-打印
断点可以通过函数名设置,也可以通过[文件路径:行数]来设置
b main
b quickjs.c:1000
打印通常包括打印基本数据类型,指针,结构体,枚举等
set print pretty on # 格式化打印
p v
p *v
p /d V
单纯从学习引擎的实现原理考虑,感觉用这几个基本指令已经够用了
据个例子
学过编译原理
的应该对过程都比较清楚,可惜我没有学过(还是忘了,一丢丢印象都没有)。先抛开所有的概念,动手调试下,看看过程。
- 测试文件 example/variable.js
const a = 1;
const b = 2;
const c = a + b;
- 进入 gdb 调试
$ gdb --args ./qjs example/variable.js
$ b main
$ b JS_CallInternal # 字节码执行函数
$ b quickjs.c:16627 # 解析字节码
$ r # 执行
$ n
...
$ s
...
$ c
- 获取字节码buf
捯饬
捯饬,....,此时断点停留在 quickjs:16627, 打印字节码(开始被这一坨给吓倒了)
# p b->byte_code_len #字节码长度
# (gdb) x/66db b->byte_code_buf # 以10进制打印每个字节大的内容
0x55acbb8a08c0: 63 60 2 0 0 -128 63 61
0x55acbb8a08c8: 2 0 0 -128 63 62 2 0
0x55acbb8a08d0: 0 -128 62 60 2 0 0 -128
0x55acbb8a08d8: 62 61 2 0 0 -128 62 62
0x55acbb8a08e0: 2 0 0 -128 -74 58 60 2
0x55acbb8a08e8: 0 0 -73 58 61 2 0 0
0x55acbb8a08f0: 56 60 2 0 0 56 61 2
0x55acbb8a08f8: 0 0 -99 58 62 2 0 0
0x55acbb8a0900: -59 40
- 字节码逻辑
按照字节码的执行逻辑格式化,第一列时操作符,后面是数据
63 60 2 0 0 -128 (OP_check_define_var) // 查看变量是否有定义, 依次对应 a, b,c
63 61 2 0 0 -128 (OP_check_define_var)
63 62 2 0 0 -128 (OP_check_define_var)
62 60 2 0 0 -128 (OP_define_var) // 定义变量,一次对应 a, b, c
62 61 2 0 0 -128 (OP_define_var)
62 62 2 0 0 -128 (OP_define_var)
-74 (OP_push_0 - OP_push_7) // 入栈
58 60 2 0 0 (OP_put_var_init / OP_put_var) // 出栈,设置到变量中
-73 (OP_push_0 - OP_push_7) // 入栈
58 61 2 0 0 (OP_put_var_init / OP_put_var) // 出栈,设置到变量中
56 60 2 0 0 (OP_get_var) // 获取变量, 入栈
56 61 2 0 0 (OP_get_var) // 获取变量, 入栈
-99 (OP_add) // 栈运算,结果入栈
58 62 2 0 0 (OP_put_var_init / OP_put_var) // 出栈,设置到变量中
-59 (OP_get_loc0)
40 (OP_return) // 返回值
能够大值猜测到:
60 2 0 0 代表 a
61 2 0 0 代表 b
62 2 0 0 代表 c
- 栈式虚拟机
quickjs是基于堆栈式虚拟机实现的,可以通过栈数据变化观察下实现逻辑
栈动作 | 栈数据 | 栈数据 |
---|---|---|
1 入栈 | 1 | |
1 出栈 | ||
2 入栈 | 2 | |
2 出栈 | ||
1 入栈 | 1 | |
2 入栈 | 1 | 2 |
取栈数据,加法运算,结果入栈 | 3 | |
3 出栈 | ||
返回值入栈 | 0 | |
返回值出栈 | 0 |
好麻烦哦,进进出出,辛苦了
,这就是栈式虚拟机的缺点
总结
算是 quickjs 调试入门了,可以看到我们js代码被翻译成自定义的字节码
,然后在自定义的虚拟机
中执行。那后面就是看下字节码生成
咯。
关注我的微信公众号"SUNTOPO WLOG",欢迎留言讨论,我会尽可能回复,感谢您的阅读。