[转]各JavaScript引擎的简介,及相关资料/博客收集帖

原文:http://hllvm.group.iteye.com/group/topic/37596

 

前一直有同学在问,于是开个帖来收集JavaScript引擎的资料链接。每个JavaScript引擎先简单写点我对它的了解,然后是资料和博客的链接。 
链接顺序按其描述的功能在产品中出现的时间从新到旧排。 
最终能做成个まとめ就好了,hmm。 

慢慢更新。把以前收集的链接都转到这帖来。先从我写的发在自己博客和HLLVM群组这边的开始。欢迎回帖补充资料或提问讨论。 

读的时候请考虑到发文时间——所有资料都只能代表当时的状况。 

以前写过篇杂谈,虽然没直接写JavaScript引擎的具体实现,不过或许会对入门有帮助:虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 
(抱歉,这系列后面烂尾了没发出来…) 
在另一帖里我简单写过几种实现解释器的方式的对比,字节码的作用之类:CPython能否用V8的方式优化性能 

有一点想补充的是:关于基于栈与基于寄存器的字节码的对比,如果在字节码的后面是一个非常简易的初级JIT编译器,那基于寄存器的字节码未必比基于栈的字节码有多少优势: 
基于栈的字节码的操作数栈(或者叫表达式栈)暗含着表达式中某些临时值的生命周期,而基于寄存器的字节码则要显式分析所有值的生命周期。这会影响到简易寄存器分配的效率。 
不过如果前面生成字节码的编译器有做一定优化,那基于寄存器的字节码还是可以重夺优势:假设有条件离线对基于寄存器的字节码做优化,那可以尝试调整其虚拟寄存器的分配,让估计更“热”的值放在更靠近编号为0的虚拟寄存器里,然后在简易JIT编译器里的寄存器分配就可以固定的把虚拟寄存器映射到真实寄存器上,这就比较好。 

========================================================================== 

Ecma-262 

还有一个没怎么见人提起的 Ecma-327 ECMAScript Compact Profile (ES-CP) 

看看Mozilla的JavaScript介绍页面上都提到了哪些JavaScript引擎: 
https://developer.mozilla.org/en-US/docs/JavaScript/About_JavaScript 

========================================================================== 

JavaScript与“无处不在的计算”或者说“普适计算” 
ubiquitous computing 

在学习JavaScript,最好的办法就是多写代码多做实验。现在几乎走到哪儿都能找到设备运行JavaScript,比当年的BASIC还要通用。 

在桌面上,主流浏览器(Chrome、Firefox、IE等)都自带开发者控制台,里面就可以直接试用JavaScript。 

不在桌面上?在网页上试用JavaScript的好地方: 
http://jsconsole.com/ 
实际上就是在网页中以控制台的形式暴露出浏览器内嵌的JavaScript引擎的功能。在平板电脑之类的不便于打开浏览器自身的控制台的地方特别好用。 

iOS上可以试试Jsany - JavaScript Anywhere 

与他人在线交流代码的好地方: 
http://codechat.net/ 

========================================================================== 

现代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) 

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

TODO 

-------------------------------------------------------------------------- 

当代JavaScript引擎之间有许多共通的实现技巧。多数优化会对JavaScript程序的行为做一定猜测(speculate),并基于猜测做激进优化(speculative optimization)。 
下面挑几个简单介绍一下。 

从源语言到中间表示的编译器(source-to-IR compiler) 
  也叫做编译器的“前端”。 
  递归下降式语法分析器(recursive-descent parser) 
  运算符优先级式语法分析器(operator precedence parser) 
  deferred parser / diet parser(延迟语法分析) 
  

从中间表示到目标代码的编译器(IR-to-target-code compiler) 
  也叫做编译器的“后端”。但因为这部分编译器经常被叫做“JIT”编译器,所以单独拿出来写 
  JIT style compiler: “just-in-time编译”狭义的定义是“即时编译”,也就是在某段代码即将第一次被执行时才对其编译。太早或太迟都不符合这个狭义版定义。所谓“JIT风格的编译器”通常意味着“编译是同步进行的”。这就自然的引出几个特征:1、编译速度必须很快;2、编译只能做有限的优化,只能选效费比高的来做。 
  optimizing compiler 
  多层编译(tiered compilation) 
  后台编译(background compilation) 
  类型反馈(type feedback) 
  类型特化(type specialization) 
  SSA-form IR 

自动内存管理 
  分代式GC(generational GC) 
  增量式GC(incremental GC) 
  并发式GC(concurrent GC) 
  准确式GC(exact / accurate / type exact / type accurate / precise GC) 

对象布局 
  紧凑对象布局 + 隐藏类 

值表现形式 
  tagger-pointer 或 tagged-value 
  NaN-boxing 

运行时系统 
  inline-cache 
  on-stack replacement 
  deoptimization 
  用native stack实现VM stack 
  cons-string 或者叫 rope 来优化字符串拼接 
  dependent string/sliced string 来优化字符串的子串操作 
  sparse array 
    B-tree 

TODO 

-------------------------------------------------------------------------- 

上面介绍的JavaScript引擎实现技巧也影响了“如何写出更高效的JavaScript代码”:尽量让代码的行为符合JavaScript引擎的猜测,效率就会高。 

写类型稳定的代码 
  在构造器函数里声明和初始化所有属性 
  尽量不要delete属性;不要通过delete属性来把某个属性重置,赋值为undefined都好 
  不要把数组当一般对象用;不要把一般对象当数组用 

TODO 

Kevin Gadd 
JavaScript Performance For Madmen 

-------------------------------------------------------------------------- 

JavaScript引擎在安全性方面面临的挑战 

JIT hardening 

Matasano Security - Attacking Clientside JIT Compilers 

http://www.accuvant.com/sites/default/files/images/webbrowserresearch_v1_0.pdf 

Alexander Sotirov 
Heap Feng Shui in JavaScript 

2013-02-19: Corelan Team (corelanc0d3r) 
DEPS – Precise Heap Spray on Firefox and IE10 

有尝试从语言级别修正部分安全问题的,例如从capability based security方向入手的Caja 

========================================================================== 

接下来介绍各JavaScript的具体情况。请允许我抱有私心的先从Oracle自家的JavaScript实现,Nashorn开始;然后介绍一个较简单干净的新JavaScript实现,lv5;接下来再介绍其它引擎,哈哈 

这些JavaScript引擎就微软和Opera的不开源,其它都是开源的。 
其实现在实现高性能JavaScript引擎的技术都不是秘密了,死守源码实在是…诶。 
结果Opera Carakan挂了,现在就剩微软系JavaScript引擎不开源。 

1楼: Oracle Nashorn 
2楼: lv5 
3楼: Google V8 
4楼: Mozilla SpiderMonkey系 (包括SpiderMonkey / Tamarin / TraceMonkey / JaegerMonkey / IonMonkey / Baseline) 
5楼: Microsoft Chakra 
6楼: Apple JavaScriptCore系 (包括JavaScriptCore / SquirrleFish / SquirrelFish Extreme (SFX) / Nitro) 
7楼: Opera Carakan 
8楼: KDE KJS 
9楼: IronJS 
10楼: Jurassic 
11楼: dynjs 
12楼: Microsoft SPUR 
13楼: Mozilla Rhino 
14楼: Digital Mars DMDScript 
15楼: Microsoft JScript (特指Chakra之前的微软的JScript) 
16楼: Microsoft Managed JScript 
17楼: Mozilla Narcissus 
18楼: Continuum

posted @ 2013-06-14 17:28  Scan.  阅读(535)  评论(0)    收藏  举报