问题描述
看起来很厉害:Kinoma | JavaScript 6th Edition。先做点笔记。
KinomaJS现在是Marvell在赞助开发。看来曾经也有华人参与开发?
KinomaJS的源码在Github:
Kinoma/kinomajs · GitHub。
开源许可证为 Apache License v2。
其中JavaScript引擎的实现名叫XS6,源码在xs6目录:
kinomajs/xs6 at master · Kinoma/kinomajs · GitHub整个引擎用C语言实现。是个很完整的JavaScript引擎实现。
“XS”自身没有版本号,后来的叫法是实现了什么版本的ECMAScript的XS就叫什么版本。所以XS6就是实现了ECMAScript 6的XS。
这里有少量开发文档:
kinomajs/notes.html at master · Kinoma/kinomajs · GitHub粗略看下来,这个JavaScript引擎的核心部分的技术水平大概跟Brendan Eich 90年代中期在Netscape写的差不多吧…
就是因为实现方式还很简单所以加入各种ECMAScript新功能才会那么快。
感觉KinomaJS要说有什么精华的话应该不在这JavaScript引擎里,而在周边库的设计与实现里吧…这我就没看了也没啥兴趣看。
解释器状态的顶层结构:struct sxMachine / txMachine。常用变量名the。
kinomajs/xs6All.h at master · Kinoma/kinomajs · GitHub可以看到它有独立的解释器栈(txMachine::stack)和作用域栈(txMachine::scope)。
它的Hosting API对解释器栈的使用方式跟Lua有相似之处:
kinomajs/xs6API.c at master · Kinoma/kinomajs · GitHubHosting API的文档:
kinomajs/Building a JS API with XS6.md at master · Kinoma/kinomajs · GitHub文档里演示的xsCall2()宏实际上就是往解释器栈里压入参数然后调用:
#define xsCall2(_THIS,_ID,_SLOT0,_SLOT1) \
(xsOverflow(-4), \
fxPush(_SLOT0), \
fxPush(_SLOT1), \
fxInteger(the, --the->stack, 2), \
fxPush(_THIS), \
fxCallID(the, _ID), \
fxPop())
词法分析器(lexer)是个典型的ad-hoc手写实现
kinomajs/xs6Lexical.c at master · Kinoma/kinomajs · GitHub语法分析器(parser)是个典型的手写递归下降式的,没有在表达式层面用运算符优先级(operator precedence parsing)算法。生成AST。
kinomajs/xs6Syntaxical.c at master · Kinoma/kinomajs · GitHub字节码生成就是典型的后序遍历AST生成线性字节码。字节码是基于栈的形式。
kinomajs/xs6Code.c at master · Kinoma/kinomajs · GitHubXS6将字节码编译器与解释器分离开来,可以允许在资源受限的使用场景中,先在宿主机器上把JavaScript源码编译成字节码序列化起来,然后在实际设备上只运行字节码(实际设备上就不需要有这个字节码编译器了)。
这是比较能体现XS6为嵌入式优化的地方。
执行引擎是个字节码解释器,可选用switch-threaded或者token-threaded(基于GCC computed goto)。
在函数调用方面没啥优化,没有使用inline cache之类的基本优化技巧。
kinomajs/xs6Run.c at master · Kinoma/kinomajs · GitHub话说这个解释器似乎在处理减法的时候有bug…我没有build来测试,但光读代码目测下面的代码会算出错误的结果:
var x = -0x7fffffff
var y = 0x7fffffff
var z = x - y // should be -4294967294
有人想测试一下不?
另外还有一个“高性能”版的解释器实现,在XS6目录之外,而在xslib里:
kinomajs/xsRun.c at master · Kinoma/kinomajs · GitHubkinomajs/xsAccelerator.c at master · Kinoma/kinomajs · GitHub这个所谓“run loop accelerator”并不是XS6的组成部分,而是老的XS(XS5?)的实现。也就是说XS的解释器曾经有比较优化的实现,但新的XS6重写了字节码编译器和解释器之后,还没有把以前的优化移植过来。
内存管理,GC堆的实现是典型的分段式(chunked),回收算法是典型的mark-sweep。
这里说的每一段叫做一个heap,是一个txSlot数组,直接malloc而来;heap之间形成单向链表,通过每个heap最开头的txSlot的next字段串起来。
kinomajs/xs6Memory.c at master · Kinoma/kinomajs · GitHub值的表现方式(value representation)是很肥的discriminated union,struct sxSlot / txSlot是也。
txSlot里的kind字段记录了tag,然后value字段记录了对应的值。
然而这个txSlot结构非常肥——它不但可以用来表示值,还可以用来表示其它各种东西,包括引用与对象自身。
所有值(txSlot)通过next字段串成一个单向链表。这个灵活的结构有多种用途。
- flag为XS_VALUE_FLAG的txSlot是值,其可能的包含的值包括function、array、string、boolean、number、regexp、host。
- kind为XS_REFERENCE_KIND的txSlot是指向对象的引用。
- kind为XS_INSTANCE_KIND的txSlot是对象自身。
txMachine::firstHeap字段记录着当前GC堆的起点。它本质上是一个txSlot数组,所有元素都是相同大小的,便于管理,完全避开了heap parsibility的问题。其它像是Array或String内容的chunk之类都必须从某些txSlot可以引用到,所以marker/sweeper只要能找到那些txSlot就可以正确处理挂在下面的chunk了。
txMachine::freeHeap字段通过next链将当前free的txSlot串在一起构成freelist。
kinomajs/xs6All.h at master · Kinoma/kinomajs · GitHub对值的操作有不少实现在xs6API里:
kinomajs/xs6API.c at master · Kinoma/kinomajs · GitHub对象布局/对象属性的实现
kinomajs/xs6Object.c at master · Kinoma/kinomajs · GitHubkinomajs/xsProperty.c at master · Kinoma/kinomajs · GitHub创建对象是这样实现的:
kinomajs/xs6Object.c at master · Kinoma/kinomajs · GitHubtxSlot* fxNewObjectInstance(txMachine* the)
{
txSlot* instance;
instance = fxNewSlot(the);
instance->kind = XS_INSTANCE_KIND;
instance->value.instance.garbage = C_NULL;
instance->value.instance.prototype = the->stack->value.reference;
the->stack->value.reference = instance;
the->stack->kind = XS_REFERENCE_KIND;
return instance;
}
也就是从txMachine::freeHeap一个新的txSlot出来,初始化到XS_INSTANCE_KIND以及栈上指定的prototype,然后的栈定的引用指向这个新的txSlot并设置其类别为引用XS_REFERENCE_KIND。
fxToInstance()函数可以从一个引用找出其指向的对象的txSlot*:
kinomajs/xs6Type.c at master · Kinoma/kinomajs · GitHub换句话说,对这样的JavaScript对象:
o = { x: 1, y: 2 }在内存里实际上是:
txSlot: o
[ reference ] txSlot
next -> [ instance ] txSlot
next -> [ x: 2 ] txSlot
next -> [ y: 2 ]
next -> NULL
数组采用紧凑布局,数组元素放在一段连续的内存里(Chunk):
kinomajs/xsProperty.c at master · Kinoma/kinomajs · GitHub数组对象标准库的实现:
kinomajs/xs6Array.c at master · Kinoma/kinomajs · GitHubXS6内部的符号驻留(symbol interning),用16位带符号整数类型txID为索引。
struct sxSymbol {
txSymbol* next;
txID ID;
txInteger length;
txString string;
txSize sum;
txInteger usage;
};
有若干内建的ID是这样处理的,例如mxID(the, _configurable):
#define mxIDs the->stackTop[-1 - mxIDsStackIndex]
#define mxID(_ID) (((txID*)(mxIDs.value.code))[_ID])
剩下的JavaScript核心库的实现也都挺常规的…
正则表达式用的是
PCRE库。
XS6内建一个简单的“profiler”,作为一个小JavaScript引擎这倒是不错。
kinomajs/xs6Profile.c at master · Kinoma/kinomajs · GitHub