我从零写了一个支持 ES5 语法的 javascript 解释器

春节前的这段时间一般都是相对轻松的。新的 okr 还没开始定,大佬很多在准备着升职答辩,让我作为一个职场小透明可以划划水。所以最近几个星期,我从零写了一个 javascript 解释器:

zhuzilin/es

为什么要写这么一个项目呢?

在这两年接触深度学习框架的过程中,由于补充了不少编译相关的知识,所以也曾多次想过要不要自己写一个编译器玩。但是一方面是我个人的不太喜欢写 toy language,因为自己设计的语言在没有使用场景的情况下很容易为了好写做很多妥协,也不方面做测试;另一方面则是 JIT compiler 这个目标无疑过庞大了,令人望而却步。

机缘巧合中,我在去年年底看了 Filip Pizlo 大大写的 Speculation in JavaScriptCore,这篇文章非常深入地介绍了现在的 JSC 的整体框架,包含了解释器和 3 层 JIT 的各自设计思路,以及动态语言如何在运行过程中积累类型信息从而在几层 JIT 之间切换(也就是标题中的 Speculation)。有这样一篇总纲指导,突然给了我一种自己也能写 JIT compiler 的错觉。

另一方面则是又刷到了 R 大之前的一篇文章:RednaxelaFX:新手上路学习JavaScript引擎实现——路线图

Constellation大大的lv5 JavaScript引擎(Constellation/iv · GitHub)就是这两者间相互反馈的一个很励志的正面故事。这个JS引擎见证了Constellation大大的学习历程,从一个非常小、非常简单、完全按照ECMAScript 5规范直观实现的小东西,进化到了颇完整、有一定水平的、现代的实现。他不断的在自己的小项目中实践,感悟各种道理,同时也不断从现有的高性能JavaScript引擎学习,吸取营养,形成了很好的反馈。

里面提到了实现一个基于 ES5 的 JavaScript 引擎可以从 “非常小、非常简单” 做起。那么能有多简单呢?不妨来试试。

最后一个很微小的原因,则是看到 mold 项目被接收进了 gcc(gcc12 新增加了对链接器 mold 的支持,对比现有的其他链接器产品,你有何评价?)mold 的作者 Rui Ueyama 最初也是自己手撸了 8cc(我不太清楚他是先写了 lld 还是 8cc...),加上前面提到的 Constellation 大大现在也是 JSC 的 reviewer 了。给我一种,如果要成为这个级别的大佬,怎么也得自己写个编译器的感觉。

那么回归 es 这个项目。目前就是处在 ES5 标准的直观实现的状态,已经实现 ES5 的全部语法,通过了绝大部分 quickjs 项目中的非 ES6 的测试,还差一些 ES5 的内置函数以及 Regex,JSON 等相对独立的模块没有实现。内置函数会在用到的时候惰性补上,后面两个我觉得和语言本身相对独立,所以估计会往后推推。

理论上来说,接下来应该介绍一下这个解释器的技术细节。不过就如 R 大所说,实现这样的一个东西还是很简单的。前端就是一个从顶至下的 parser,运行时部分我觉得读过 Essentials of Programming Languages 的前几章就足够了(插个题外话,eopl 真的是我这两年看得最开心的技术书了)。所以为了提升一下本文的技术含量,来说一下这个项目之后的规划。

首先是近期目标(几个月):

  • 垃圾回收:现在这个项目还没有写垃圾回收(后文都直接叫 gc 了)。直接糊一个 mark and sweep 虽然比较简单,但是我还是想先去广泛地了解一下主流的 js engine(V8、JSC、SpiderMonkey、Hermes、quickjs)里的设计方式,然后选一个工作量能 hold 住的方案。如果调研好了应该也会写篇知乎文章分享一下;
  • Module:ES5 标准中是没有模块化支持的,所以需要考虑支持一下 ES6 或者是 CommonJS 的 module。前者的好处是 ES6 和 ES5 一样是有非常规范的标准的,而后者可能要参照这 Node.js 去写了;后者的好处则是可以方便之后支持 Babel,因为 Babel 把 ES6 转译成 ES5 的时候好像就会把 import 转成 require。有了 module 之后也可以简单支持一些简单的原生模块,例如 console (现在只是手写了一个 console.log 方便使用),fs 等等。

中期目标(一至两年):

  • 给解释器设计 bytecode:一个高效的解释器肯定是要有一个 bytecode 的。这部分也是要好好学习一下各方的设计。暂定会去学习 JSC 做一个 register based。不过 JSC 好像是为了方便控制 frame 来做 JIT,解释器是写在一个自己设计的类似汇编的语言上的,不是很确定我是不是也要这么搞...
  • 解释器的加速技术实验:有了字节码之后,就可以做各种类型的解释器优化了。目前我最想尝试的是 inline caching 和各种dispatch 方案。

长期目标(三至五年):

  • JIT compiler:应该会去实现单层的 JIT,其中的优化估计会限制在 mir 这个项目的 pass 里或者是 JSC 的 DFG JIT 的范围,也就是不会做 LICM 这种类型的复杂度高,编译时间长的东西。具体的设计还完全没有头绪,要补的东西还太多。

一些很可能不会做的 optinal 目标:

  • Node.js binding:之前测试的时候本来想过通过写一个简单的 node binding 的方式来做测试的。不过因为找到了 quickjs 的测试来做替代,所以就暂时放弃了。简单调查了一下 Node.js on ChakraCorespidernode,好像都是要写一个 V8 wrapper,所以工作量成迷。如果可以简单地 bind 一部分,让我能跑个 express 啥的就好了;
  • JVM bytecode:如果最后设计的 bytecode 方便 offload 到 JVM 上的话,可以试一下,方便做 benchmark,顺便也学习一下最有名的 stack based bytecode 的设计。

我一直很喜欢那种需要日积月累的东西,学语言也好,健身也好,就像那个拿小锤敲巨石的故事一样,我相信时间中蕴含的巨大力量。对于程序员来说,这种日积月累可能只能体现在 side project 上。所以我希望自己能有这个福气把这个项目坚持下去。

对了,虽然 es 还处在很早期的阶段,还是求一波 star!运行过程中出了啥问题,也欢迎来提 issue~

おすすめ

転載: juejin.im/post/7054388481916665864