JavaScript引擎实现

转自 http://rednaxelafx.iteye.com/


现代JavaScript引擎都有哪些特征呢?跟以前的JavaScript引擎有怎样的差别,为什么变快了那么多?这里简单写下我的理解吧。

有很多同学可能会想从JavaScript引擎的源码着手一探究竟。这里也顺便介绍一下JavaScript引擎大致的组成部分与工作流程。了解这其中涉及的各种术语都是什么意思的话,读源码就能事半功倍,很多时候光看文件名就足以定位到自己关心的那部分实现。 <- TODO

早期JavaScript引擎的实现普遍跟同时代的其它脚本语言一样,比较“偷懒”。反正是“脚本语言”,当时的JavaScript脚本通常只包含很简单的逻辑,只运行很短时间就完事。没啥性能压力,得不到足够的重视与开发资源,性能自然是好不到哪里去,却也足以满足当时的需求。

非常早期的“Mocha”引擎实现得确实非常偷懒。字节码解释器、引用计数方式的自动内存管理、fat discriminated union形式的值表现形式。现在随便找本教写玩具语言实现的书上或许就会这么教…但只能用来写玩具 犀牛书第4版写了点JavaScript与引用计数的历史。
到1996年,Brendan Eich新写的SpiderMonkey已经改为使用mark-and-sweep GC、tagged value。

于是其实早期的两个主要的JavaScript引擎实现,Mozilla SpiderMonkey和Microsoft JScript其实都一直在用mark-and-sweep GC。也没啥别的主流JavaScript引擎用过引用计数方式来实现自动内存管理的。这点别被忽悠了。在叫得出名字的JavaScript引擎里只有quad-wheel(没听说过么?不奇怪,非主流嘛)是用引用计数方式实现自动内存管理的。
(老版本IE里JScript虽说是有因为循环引用而导致内存泄漏的问题,但那不是因为JScript自身用引用计数。问题出在JScript与DOM交互的边界上:IE的DOM节点(及其它host对象)是COM对象,而COM对象自身是引用计数的。这导致JScript与DOM交互时有可能被连累引发循环引用->内存泄漏的问题。IE9/Chakra里已经通过把DOM对象变成由JavaScript一侧来管理解决了这个问题。)

几种较老的JavaScript引擎的特征:

  SpiderMonkey JScript KJS
实现语言 C C++ C++
执行模式 解释执行 解释执行 解释执行
解释器 字节码解释器:基于栈的字节码 字节码解释器:基于栈的字节码 树遍历解释器
动态编译器
自动内存管理 mark-and-sweep mark-and-sweep mark-and-sweep
对象布局 ? 基本上是HashTable ?
针对密集数组的优化 ? 无 (JScript < 5.7);有(JScript 5.8) ?
Inline-cache ? ? ?
值表现形式 tagged-value 堆对象 堆对象
Function.prototype.toString() 从字节码反编译 ? ?

(几个术语:
树遍历解释器:tree-walking interpreter。遍历抽象语法树来解释执行的解释器。
对象布局: object representation 或者 object layout。指在堆上分配的JavaScript对象的在内存中的布局。
值表现形式: value representation。注意跟“对象布局”说的不是一件事。这个指的是原始类型数据、指向堆上分配的对象的指针之类的值的表现形式。对某些JavaScript引擎来说这是指“JSValue”背后在内存中的表现形式。

TODO 加上对parser的描述
SpiderMonkey KJS JavaScriptCore V8 Managed JScript
手写纯递归下降式 bison生成LALR(1) bison生成的LALR(1) 手写的递归下降+运算符优先级混合式 手写的纯运算符优先级式


早期JavaScript引擎得到的投入实在不足,而当时的Java虚拟机(JVM)却得到了大量资源实现各种优化,包括JIT编译器之类。这使得用Java写的Rhino一度能比用C写的SpiderMonkey跑得还快,因为Rhino得益于JVM里优秀的JIT编译器和GC,而SpiderMonkey还在用简易的解释器和GC。

这个阶段中,JavaScript对象的布局或者说表现方式通常可以叫做“property bag”,本质上就跟hashmap一样。

在Google推出V8之后,业界受到巨大冲击。V8的性能远高于当时所有其它JavaScript引擎,可以有效支撑起当时兴起的大量使用JavaScript的Web应用。
各大JavaScript引擎的实现者都坐不住了,像打了鸡血似的使劲优化优化再优化。先是把已在其它HLLVM上得到充分验证的优化技术引入到JavaScript引擎中,然后再针对JavaScript语言的特点做专项优化。

现在(2013-04)几种主流的JavaScript引擎的特征:
  V8 SpiderMonkey Chakra Nitro Nashorn
实现语言 C++/汇编 C++ C++ C++/汇编 Java
执行模式 纯编译: 两层编译 解释/编译混合式: 3层执行模式 解释/编译混合: 2层执行模式,后台编译 解释/编译混合: 3层执行模式 纯编译
解释器 字节码解释器 字节码解释器:基于寄存器的字节码 字节码解释器 LLInt:基于寄存器的字节码
动态编译器 初级编译器 + 优化编译器 初级编译器 Baseline + 优化编译器 IonMonkey 初级编译器 method JIT + 优化编译器 DFG JIT
自动内存管理 分代式GC: 初生代: copying收集器; 年老代: 增量式mark-and-sweep, 可选compact 分代式GC 分代式GC: 初生代: copying收集; 年老代: 并发式mark-and-sweep 分代式GC 依赖于底层JVM的GC
对象布局 紧凑+隐藏类 Map 紧凑+隐藏类 Shape 紧凑+隐藏类 紧凑+隐藏类 Structure 紧凑+隐藏类 PropertyMap
针对密集数组的优化
Inline-cache MIC/PIC PIC PIC PIC MIC/PIC
值表现形式 tagged-pointer / IEEE 754 double / integer pun-boxing tagged-value NaN-boxing   堆对象 / integer
正则表达式 编译 Irregexp 编译 编译 编译 WREC 混合
Function. prototype. toString() 保留源码原文 (2012年7月前) 从字节码反编译; (761723后) 保留源码原文  ? ? 保留源码原文

(几个缩写:
copying GC: 也叫 scavenger。
MIC: monomorphic inline-cache
PIC: polymorphic inline-cache
pun-boxing: Packed NaN unboxing)

SpiderMonkey和LuaJIT似乎都在用pun boxing

所以说这年头是个JavaScript引擎都得有JIT编译器了…没有都不好意思出来混。受到平台限制(例如iOS、Windows Phone)而无法实现JIT编译器的“第三方JavaScript引擎“只好哭了。

TODO

你可能感兴趣的:(JavaScript引擎实现)