SpiderMonkey 设计和实现 Author:张平 Email:p.zhang.9.25@gmail.com
简介: SpiderMonkey: JavaScript Engine: Javascript发明者Brendan Eich 在NetScape所写,后来由Mozilla Foundation所维护。为第一款javascript引擎。 采用C编写,可移植性非常好。代码质量高,可维护性。满足ECMA-262规范。
目前状况: 最新版为1.8,且已经停止维护。1.8使用c++实现。firefox3.0仍使用SpiderMonkey. Firefox较新的js引擎:TraceMonkey,JägerMonkey,IonMonkey。他们都是从SpiderMonkey发展而来,主要是添加了一些优化以及JIT支持,大部分代码跟SpiderMonkey一样。
综述: 引擎可作为一个独立的dll,提供Jsapi.h供其他应用使用.也可直接集成进应用(Browser)。 模块: Interpreter: 在一个大的函数中,采用switch语句,每次执行一个bytecode的方式执行js代码。 所有解释器的状态保存在一个JSContext中,所以SpiderMonkey绝大部分函数都要带一个参数JSContext。 每个script或者函数执行,会创建一个上下文,引擎称之为Stack Frame, js权威指南称之为execution context.
compiler: compiler将js source code转换成script对象,script对象包含bytecode, source annotations, a pool of string等。 compiler包含以下几个模块: 1, parser: lexical scanner, recursive-descent parser , 2, tree-walker code generator 3, decompiler: functionFoo.toSource()
memory manager and garbage collector: 机制:mark-sweep. content:JS Object, string, double. exact collector :因为SpiderMonkey的GC是强引用,所以C/C++代码要确保GC的可达性。 JavaScript Values: Tagged Pointer:使用指针的末几位来区分不同的type(Object, number, string)。js对象都是8字节对齐,所以最多可以利用对象地址的最后三位。 standard library: Function,Object,Array,String等ECMA规范要求支持的对象的实现。 Error Handling: SpiderMonkey内部函数没有错误码返回,错误信息通过exception抛出。内部函数返回FALSE表示出错。 debugger: JIT:
SpiderMonkey的内存管理: 动态内存管理: 依据对象的size,譬如first fit,bestfit,伙伴系统等。 依据对象的生命周期:譬如stack,引用计数,gc等。 理论基础: Fast Allocation and Deallocation of Memory Based on Object Lifetimes. Author : David Hanson. 对象可能在运行时各个时间点创建,但是他们都是在一个时间点生命终结。 譬如:编译的时候,一张网页的DOM,RenderObject等。
中间存在少量的释放可以忽略掉。该算法分配时间是O(1),释放时间为0.
SpiderMonkey gc: JSObject,string, double可以被GC回收。 GC根据mark-sweep来回收内存。 GC时机: 新的js对象已经无内存可供分配。 当一个js的context删除,可能触发GC,根据当前引擎使用的内存的量是否超过某一阀值,这一阀值可定制。 脚本主动触发。浏览器环境下并没有提供相关功能。 GC算法注意事项: 速度。(gc本身速度要快,能少执行尽量少执行) 递归算法,需防栈溢出。
GC递归的优化: 消递归:Automated Verification of the Deutsch-Schorr-Waite Tree-Traversal Algorithm。 参考:http://www.cs.wisc.edu/wpis/papers/sas06-dsw.pdf
JS Object Internal: JS Object内存上8字节对齐(tagged pointer),用hash table保存了Object的属性和值。 几个我们需要关心的property: __proto__: 指向constructor的prototype, 他的值是一个对象或者NULL。他用来基于prototype-inheritance的属性的寻找。 2. __parent__: js的scope为lexical(static) scoping. __parent__指向了lexical scoping的parent。
JS 原型链: class-based: class:定义对象所需要的方法和数据的类型。 instance:对象的数据。通过class 的 constructor创建。 关注:类的类型和类之间的关系。 prototype-based(self): class-less:没有类。 Instance: 通过拷贝prototype, 通过引用prototype。 关注:对象的行为。
JS prototype chain: prototype: 原型与constructor对应,每个函数对象(constructor)都有prototype对象。 __proto__: 原型链的链指针,指向一个prototype对象。
JS Scope Chain: scope:值和表达式所对应的封闭的上下文。 变量声明和定义的场所。 表达式定义和执行的场所。 嵌套。 dynamic scoping: 变量随着控制流的进入而绑定在stack上,随着控制流的结束而从stack上弹出。bottom stack上的变量将屏蔽top stack上的变量。(C++ statement block) 无法在编译时期确定非局部变量(free variables)的作用域,故称之为dynamic scoping。
Example: int x = 0; int f() { return x; } int g() { int x = 1; return f(); } print g(); 输出1. 好处:变量的值动态绑定。函数的行为根据 系统当前的状态而变化。 坏处:程序员需要小心处理函数执行状态的 变化(不要假设当前系统的状态); 无法实现纯粹的函数式编程。 例子:block.
lexical(static) scoping: 变量始终指向同一个执行上下文。它不依赖执行时的上下文,在编译期已经确定。 Example: int x = 0; int f() { var x; return x; } int g() { int x = 1; return f(); } print g(); 输出0.
JS Scope chain: 每一段js脚本或者函数执行的之前,都有一个Scope chain Object创建起来,并放置在当前执行的上下文中(stack frame)。 变量名的寻找从Scope chain Object开始,往其scope chain的上一层(parent)递归。
链的体现: 对象创建发生在一个scope中,创建时会设置属性__parent__为当前的scope chain object。global.__parent__ === null; scope是一个对象(scope chain object): script执行的时候,scope chain为global object。 函数执行的时候,scope chain为function constructor的__parent__。
函数的定义: 函数对象在编译的时候已经创建。 funObj.__proto__ = Function.prototype; funObj.__parent__ = StackFrame.scopeChain; 函数的执行: StackFrame.scopeChain = funObj.__parent__;
看看代码,看看实例
closure: A closure is a function together with a referencing environment for the nonlocal names of that function. function: 闭包返回的函数对象为原函数对象的拷贝。 同一函数可创建多个闭包。 environment: 在引擎中称为call object.他记录了外层函数所有的参数,局部变量。每一个闭包一个call object。闭包返回的函数的__parent__指向call Object。 函数如果生成闭包,会在初始化执行上下文(Stack frame)的时候创建call object,并将其作为scope chain。 close: 只能通过返回的函数访问nonlocal variables.
function foo(){ var a = 100; function foo1(){ return ++a; } return foo1; } var f = foo(); f();
Js 引擎初始状态内部对象状态:
JavaScript 脚本的执行: 流程: 1, parse, 生成语法树。 2, byte code generation, 生成执行码(opcode)。 3, interpret, 解释执行。
Compile: parse,生成语法树: 根据运算符的优先级,采用递归降解的方式解析脚本。
2. bytecode generation: 同时还会生成source annotations : 用于decompile. prolog(predefined) section: 用于存储变量声明,函数定义的byte code。在脚本执行的时候,prolog section首先被执行。 main section: 保存主执行码。在prolog section执行结束后执行。 在生成byte code的时候,会计算出脚本或者函数需要的最大的栈空间大小。
compiler的结果: Script 一段脚本在编译,生成byte code之后,需要用script对象来存储byte code, source annotations, 符号表,脚本运行所需要的最大栈size等信息。
解释执行(interpreter): 执行对象:脚本or函数. 执行byte code前需要准备好的上下文(stack frame): parameters, variables scope chain object, call object, this object stack 这些信息存放在stack frame上。stack frame也可以理解成execution context.
parameters, local variables的寻址: normal object, global variables都是作为其他对象的属性而可被访问。 参数和局部变量在运行时才可访问,存储在执行上下文Stack Frame中。 生成闭包时,parameters,local variables都将作为call object的属性而被访问。
this 脚本执行的时候,this为当前的scope chain object。 当函数执行的时候,this为当前函数的调用对象,如果不存在,为函数的scope chain的root(global对象)。
4, scope chain object, call object: script执行: scope chain object为global对象。 函数执行: scope chain object为函数对象的__parent__. 当存在闭包,scope chain object为call object。
看看代码也许更直接
执行byte code:
一个较为综合的例子: with语句: with语句会临时修改当前的scope chain object. frames[1].document.forms[0].address.value oops… It’s too long. with(frames[1].document.forms[0].address) { value = ""; }
var obj = {a:100}; with(obj){ function foo(){ return ++a; } } alert(foo());
js debug功能: watch: 当对象属性变化的时候,回调到注册的函数。当属性删除的时候watch依然存在。 unwatch: watch通过__setter__来实现,watch数据存放在全局js句柄上。 watch为Object.prototype的方法,所有对象都将继承。
Trap: 当执行到某一个byte code的时候,调用注册的回调函数,根据回调函数的返回值确定原来的脚本执行与否。 JSOP_NAME 0 find a variable JSOP_TRAP if (CONTINUE == trap_handler()) JSOP_NAME 0; else return; 可以使用trap来实现breakpoint。 此功能引擎没有开发给脚本开发人员。
hook JS_SetSourceHandler JS_SetExecuteHook JS_SetCallHook JS_SetObjectHook JS_SetThrowHook JS_SetDebugErrorHook JS_SetNewScriptHook JS_SetDestroyScriptHook
decompile: JS_PCToLineNumber JS_LineNumberToPC JS_GetFunctionScript 配合JS_LineNumberToPC和trap可以设置在某一行的断点。
Evaluating debug code JS_EvaluateInStackFrame 在执行函数或者脚本前执行自定义的脚本。可以实现函数的step-in, step-out. 其他: Memory usage Examining object properties Inspecting the stack 等。
SpiderMonkey thread safety: JSRunTime: SpiderMonkey的全局句柄,不管有多少线程, 只会实例化一次。 Container:用来存储全局信息,譬如string pool, keyword, GC 等。 JSContext: js脚本运行的环境。SpiderMonkey可能存在多个JSContext,每一个JSContext有一个global Object。 JSContext之间可以相互访问。 对应浏览器环境下,每个frame对应一个JSContext。
线程安全: 对JSRunTime的访问需要加锁。 GC出发的时候也会加锁,其他线程对GC的访问将被会阻塞。 需要引起注意的一点:JSContext可以相互访问,他们可以引用其他JSContext下的JS Object, 在做SpiderMonkey开发的时候需要注意。 浏览器会有SecurityOrigin来确定跨域访问的安全性。
杂谈: getter,setter: JS Object property adaptor。当读取或者写入对象某一属性的时候,执行注册的函数。 js event: 很遗憾,js引擎没有提供事件,异步回调等机制,这些都由js client实现。
参考: http://ftp.mozilla.org/pub/mozilla.org/ https://developer.mozilla.org/en/SpiderMonkey http://ejohn.org/blog/javascript-getters-and-setters/ http://en.wikipedia.org/wiki/Scope_(computer_science) http://en.wikipedia.org/wiki/Prototype-based_programming http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)